21
\$\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
    Commented Sep 9, 2021 at 4:57
  • \$\begingroup\$ paths|map(@text)|join(".") \$\endgroup\$
    – Sara J
    Commented Oct 6, 2021 at 18:45

10 Answers 10

9
\$\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
    Commented Nov 8, 2019 at 8:24
  • \$\begingroup\$ awesome, you rock! \$\endgroup\$
    – Chau Giang
    Commented Nov 8, 2019 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\$ Commented Sep 14, 2021 at 16:04
  • 1
    \$\begingroup\$ @danielassayag You should read this as k = (s ? s + [,k] : k). \$\endgroup\$
    – Arnauld
    Commented Sep 14, 2021 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
    Commented Sep 14, 2021 at 16:22
5
\$\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
5
\$\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
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\$
3
\$\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\$
3
\$\begingroup\$

PHP, 147, 146, 145, 120, 99 bytes

function f($a,$t=''){foreach($a as$k=>$v){$t1=$t?"$t.$k":$k;echo"$t1\n";!is_array($v)?:f($v,$t1);}}

Try it online!


/** Assume decoded json */
$array = json_decode('{
    "name" : {
        "first": "jane",
        "last": "doe"
    },
    "lang" : [
        "html", 
        "css"
    ]
}', true);

formatArrayKeys($array);

/** 
 * Extract all multidimensional array keys. 
 * The result will be as:
 * - firstKey
 * - firstKey.firstSubKey
 * - firstKey.firstSubKey.firstSubSubKey
 * - firstKey.secondSubKey
 * - secondKey
 * - etc.
 */
function formatArrayKeys($array, $acc = ''){
    foreach($array as $key => $value) {
        $newAcc = $acc ? "$acc.$key" : $key;
        echo "$newAcc\n";
        !is_array($value) ?: formatArrayKeys($value, $newAcc);
    }
}
\$\endgroup\$
1
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\$

Julia 1.0, 57 bytes

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

Try it online!

\$\endgroup\$
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\$

Go, 256 bytes

import."fmt"
func w(l string,j map[string]any)(a[]string){S:=Sprintf
for k,v:=range j{q:=k
if l!=""{q=S("%s.%s",l,q)}
a=append(a,q)
switch v:=v.(type){case []any:for i:=range v{a=append(a,S("%s.%d",q,i))}
case map[string]any:a=append(a,w(q,v)...)}}
return}

Attempt This Online!

Takes a parsed JSON object as a map[string]any and the initial key, which starts as "", as input.

Explanation

import."fmt"
func w(l string,j map[string]any)(a[]string){
S:=Sprintf
for k,v:=range j{                                     // for each key and value pair...
q:=k;if l!=""{q=S("%s.%s",l,q)}                       // if the starting key is not empty; i.e. not at top level, append it to the current key
a=append(a,q)                                         // add to the output
switch v:=v.(type){                                   // switch on the value type, where...
case []any:for i:=range v{a=append(a,S("%s.%d",q,i))} //      an array has each index added
case map[string]any:a=append(a,w(q,v)...)}}           //      a nested object recurses
                                                      //      otherwise do nothing
return}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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