Extract all keys from an object (json)

12

3

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

chau giang

Posted 2019-11-08T02:29:12.913

Reputation: 725

Answers

4

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'

Arnauld

Posted 2019-11-08T02:29:12.913

Reputation: 111 334

fantastic, your answer beats @tsh, I am just curious what o.big is, could you please tell me about it? – chau giang – 2019-11-08T08:24:35.033

awesome, you rock! – chau giang – 2019-11-08T08:33:44.697

@tsh I've asked the OP if we need to support that kind of input. (Your solution has the same problem.) – Arnauld – 2019-11-08T10:02:09.833

4

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!

Value Ink

Posted 2019-11-08T02:29:12.913

Reputation: 10 608

3

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!

tsh

Posted 2019-11-08T02:29:12.913

Reputation: 13 072

3

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.

Chas Brown

Posted 2019-11-08T02:29:12.913

Reputation: 8 959

2

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

Galen Ivanov

Posted 2019-11-08T02:29:12.913

Reputation: 13 815

1

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.

Nick Kennedy

Posted 2019-11-08T02:29:12.913

Reputation: 11 829

1

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,"."]&,#,∞]&

LegionMammal978

Posted 2019-11-08T02:29:12.913

Reputation: 15 731