18
\$\begingroup\$

Description

Given an object (json), write code to extract all the keys from it. This is a question that I really wanted to ask everybody for a long time ago but I did not have time to write it up. It can be helpful in some cases in your daily work.

Rule:

  • You can use any parse function to get the JSON object, it does not cost you any bytes
  • Since a JSON object is a data structure that is not available in some languages, you can use any kind of data that has a similar structure in your favorite language.
  • To be clear, the input to your function should be a JSON object if it can.
  • The object can have nested keys.
  • The object can contain arrays and if it contains an array you will use the index of each element as a key.
  • Output can be a list with any format but it is preferred to be line by line.
  • The output keys can be arranged in any order, it does not matter.
  • Shortest bytes for each language will be win.
  • You can use any delimiter between each child key and its parent. Here I use . (please see the sample output for more detail).
  • The key can be a special character. For example: The input I got from @tsh
{"":{"":4,".":5},".":2,"..":3}

But this is a special case and it is not required to handle it. You are better to avoid it.

Example

Given an object:

A = {
"name" : {
  "first": "jane",
  "last": "doe"
},
"lang" : ["html", "css"]
}

Then the output should be:

"name"
"name.first"
"name.last"
"lang"
"lang.0"
"lang.1"

The index key (0 and 1) are a little tricky here, so for some languages like Javascript, it can be [0] and [1] instead.

So the example below is also correct:

"name"
"name.first"
"name.last"
"lang"
"lang[0]"
"lang[1]"

A Sample test case:

Input:

{
    "quiz": {
        "sport": {
            "q1": {
                "question": "Which one is a correct team name in the NBA?",
                "options": [
                    "New York Bulls",
                    "Los Angeles Kings",
                    "Golden State Warriors",
                    "Houston Rockets"
                ],
                "answer": "Houston Rockets"
            }
        },
        "maths": {
            "q1": {
                "question": "5 + 7 = ?",
                "options": [
                    "10",
                    "11",
                    "12",
                    "13"
                ],
                "answer": "12"
            },
            "q2": {
                "question": "12 - 8 = ?",
                "options": [
                    "1",
                    "2",
                    "3",
                    "4"
                ],
                "answer": "4"
            }
        }
    }
}

Output:

[
  "quiz",
  "quiz.sport",
  "quiz.sport.q1",
  "quiz.sport.q1.question",
  "quiz.sport.q1.options",
  "quiz.sport.q1.options.0",
  "quiz.sport.q1.options.1",
  "quiz.sport.q1.options.2",
  "quiz.sport.q1.options.3",
  "quiz.sport.q1.answer",
  "quiz.maths",
  "quiz.maths.q1",
  "quiz.maths.q1.question",
  "quiz.maths.q1.options",
  "quiz.maths.q1.options.0",
  "quiz.maths.q1.options.1",
  "quiz.maths.q1.options.2",
  "quiz.maths.q1.options.3",
  "quiz.maths.q1.answer",
  "quiz.maths.q2",
  "quiz.maths.q2.question",
  "quiz.maths.q2.options",
  "quiz.maths.q2.options.0",
  "quiz.maths.q2.options.1",
  "quiz.maths.q2.options.2",
  "quiz.maths.q2.options.3",
  "quiz.maths.q2.answer"
]

This is my solution using jq:

jq -r '[paths|map(.|tostring)|join(".")]'

Full code:

jq -r '[paths|map(.|tostring)|join(".")]' file.json

The content of file.json is an object from input

\$\endgroup\$
2
  • 1
    \$\begingroup\$ [paths|map(tostring)|join(".")] \$\endgroup\$
    – Razetime
    Sep 9 at 4:57
  • \$\begingroup\$ paths|map(@text)|join(".") \$\endgroup\$
    – Sara J
    Oct 6 at 18:45

10 Answers 10

6
\$\begingroup\$

JavaScript (V8), 72 bytes

An edited version to support literal false, true and null values.

f=(o,s)=>!o|[o]==o||Object.keys(o).map(k=>f(o[k],k=s?s+[,k]:k,print(k)))

Try it online!


JavaScript (V8), 69 bytes

Takes a native JSON object as input. Prints the results, using a comma as the delimiter.

f=(o,s)=>[o]==o||Object.keys(o).map(k=>f(o[k],k=s?s+[,k]:k,print(k)))

Try it online!

How?

This is a recursive function walking through the keys at the root level and then in each sub-tree of the structure.

We need to process recursive calls on objects and arrays and to stop on strings and numbers. This is achieved with [o]==o||Object.keys(o):

 type of o | [o]==o    | Object.keys(o)  | string coercion example
-----------+-----------+-----------------+-------------------------------------
 array     | false     | 0-based indices | ['foo', 'bar'] -> 'foo,bar'
 object    | false     | native keys     | {abc: 'xyz'}   -> '[object Object]'
 string    | true      | n/a             | 'hello'        -> 'hello'
 number    | true      | n/a             | 123            -> '123'
\$\endgroup\$
5
  • \$\begingroup\$ fantastic, your answer beats @tsh, I am just curious what o.big is, could you please tell me about it? \$\endgroup\$
    – Chau Giang
    Nov 8 '19 at 8:24
  • \$\begingroup\$ awesome, you rock! \$\endgroup\$
    – Chau Giang
    Nov 8 '19 at 8:33
  • \$\begingroup\$ what does k=s mean? is that a condition test or an assignment of value..? does k=s sets k to equal s? But why is it in an if statement? or does it means if k==s? HELP \$\endgroup\$ Sep 14 at 16:04
  • 1
    \$\begingroup\$ @danielassayag You should read this as k = (s ? s + [,k] : k). \$\endgroup\$
    – Arnauld
    Sep 14 at 16:05
  • 1
    \$\begingroup\$ @danielassayag Well, it could be written as if(s) k = s + [,k]; else k = k; but it's really just if(s) k = s + [,k]; since the else is pointless. However, you can't use a if within a function call anyway so you'd have to rewrite it entirely: .map(k=>{v=o[k];if(s)k=s+[,k];print(k);f(v,k);}). \$\endgroup\$
    – Arnauld
    Sep 14 at 16:22
4
\$\begingroup\$

Python 2, 122 135 122 119 bytes

f=lambda d:`d`[0]in'{['and sum([[str(k)]+['%s.'%k+q for q in f(v)]for k,v in(enumerate,dict.items)['{'<`d`](d)],[])or[]

Try it online!

Now handles an even broader class of inputs, including lists of dicts.

\$\endgroup\$
4
\$\begingroup\$

Ruby, 108 146 115 92 bytes

+38 bytes to fix test cases for objects inside arrays.....

-31 bytes because we can take a parsed JSON object as input now.

-17 bytes by removing the duplicated flat_map usage.

f=->j,x=''{z=j==[*j]?[*0...j.size]:j.keys rescue[];z.flat_map{|k|[r=x+k.to_s]+f[j[k],r+?.]}}

Try it online!

\$\endgroup\$
0
3
\$\begingroup\$

JavaScript (Node.js), 75 bytes

f=o=>Object.keys(o+''===o||o||0).flatMap(k=>[k,...f(o[k]).map(i=>k+'.'+i)])

Try it online!

\$\endgroup\$
0
2
\$\begingroup\$

Red, 159 bytes

func[s][r: :rejoin g: func[s m][foreach k keys-of m[print p:
r[s k]if map? t: m/:k[g r[p"."]t]if block? t[repeat n length?
t[print r[p"."n]]]]]g ""load-json s]

Doesn't work in TIO since load-json was introduced recently, but works fine in the Red console: enter image description here

\$\endgroup\$
2
\$\begingroup\$

PHP, 147 146 bytes

function f($j,$t){foreach($j as$k=>$v){if(is_array($v)){echo trim($t.="$k.",'.')."\n";f($v,$t);$t=str_replace("$k.",'',$t);}else{echo$t."$k\n";}}}

Try it online!

I'm sure that this is far from best solution in PHP, but I tried my junior talent. :D

$json = '{
    "name" : {
        "first": "jane",
        "last": "doe"
    },
    "lang" : [
        "html", 
        "css"
    ]
}';

/** We need to define a text string variable. */
$text = ''; // or keysChainString

/** Assume that our json is decoded. */
$array = json_decode($json, true);

/** 
 * Function that extract all multidimensional array keys. 
 * The result will be as:
 * - firstKey
 * - firstKey.firstSubKey
 * - firstKey.firstSubKey.firstSubSubKey
 * - firstKey.secondSubKey
 * - secondKey
 * - etc.
 */
function extractAndFormatMultidimensionalArrayKeys($array, $text){
    /** 
     * Iterate through array. 
     */
    foreach($array as $key => $value) {
        /** 
         * Check if key value is an array.
         * Echo key appended to previously declared $text variable.
         * Then make a recursive call to this function on that array.
         * After that, delete the key from text string.
         */
        if(is_array($value)) {
            echo trim($text .= "$key.", '.') . "\n";
            extractAndFormatMultidimensionalArrayKeys($v, $t);
            $text = str_replace("$key.",'',$text);
        /** 
         * If the value is not an array, echo the text concatenated with the key. 
         */
        } else {
            echo $text . "$key \n";
        }
    }
}
\$\endgroup\$
1
1
\$\begingroup\$

R, 114 bytes

f=function(x){if(is.null(n<-names(x)))n<-seq(x);unlist(Map(function(y,z)c(z,if(is.list(y))paste(z,f(y))),x,n),,F)}

Try it online!

Defines a recursive function which takes an R list, possibly named, and returns the list of names using space as a separator. Here, a named list (or sub list) corresponds to an object in JSON, and an unnamed list corresponds to an array in JSON.

\$\endgroup\$
1
\$\begingroup\$

Wolfram Language (Mathematica), 39 27 bytes

MapIndexed[Echo@#2&,#,∞]&

Try it online! This function represents JSON arrays as Lists and objects as Associations. The function takes a JSON object as input and prints a set of part specifications to the standard output, each on their own line and preceded by >> . A part specification is a list of indices, where each index is a 1-based number or a string wrapped in Key. The index of a parent object is printed after those of its children. The output for the first example is:

>> {Key[name], Key[first]}
>> {Key[name], Key[last]}
>> {Key[name]}
>> {Key[lang], 1}
>> {Key[lang], 2}
>> {Key[lang]}

If the Key wrapper is undesirable, it can be removed using a 36-byte function:

Wolfram Language (Mathematica), 36 bytes

MapIndexed[Echo[#2/.Key->N]&,#,∞]&

The original challenge's formatting can be achieved with a 54-byte function:

Wolfram Language (Mathematica), 54 bytes

MapIndexed[Print@StringRiffle[#2/.Key->N,"."]&,#,∞]&
\$\endgroup\$
1
\$\begingroup\$

GO

//source is the json object and parent initially is ""
func getFieldName(source string,parent string)[]string{
    res := make([]string,0)
    var arr map[string]gjson.Result
    if gjson.Parse(source).IsObject(){
        arr = gjson.Parse(source).Map()
    }else{
        return  []string{parent}
    }
    for key,val := range arr{
        var temp []string
        if parent == ""{
            temp= getFieldName(val.Raw,key)
        }else{
            temp = getFieldName(val.Raw,parent + "." + key)
        }
        res = append(res,temp...)
    }
    return res
}
\$\endgroup\$
1
  • \$\begingroup\$ Welcome to the site. Remember to add your score, and check out this page. \$\endgroup\$ Aug 10 '20 at 14:56
0
\$\begingroup\$

Julia 1.0, 57 bytes

!x=[(k->["$k";"$k.".*!x[k]]).(keys(x))...;]
!x::String=[]

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.