Extract all keys from an object (json)

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"
],
}
},
"maths": {
"q1": {
"question": "5 + 7 = ?",
"options": [
"10",
"11",
"12",
"13"
],
},
"q2": {
"question": "12 - 8 = ?",
"options": [
"1",
"2",
"3",
"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.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.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",
]


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

• What's your victory criteria? I assume code golf? – Value Ink Nov 8 at 2:32
• Is {"":{"":4,".":5},".":2,"..":3} valid input? What output is expected? – tsh Nov 8 at 3:23
• @JoKing, you are right, I think it is okay to user parse function or you can take an equivalent object or use your languages that has similar data structures. I just updated my question, please take a look at it. – chau giang Nov 8 at 6:25
• @chaugiang, there is no need to .|tostring inside map(), just tostring is enough. But as it is code golf, is shorter with "\(.)". – manatwork Nov 8 at 10:36
• @chaugiang When you say "it's up to you, it's better to avoid them" it does not help clarify. What you really mean is that we can assume they are not present. Can you please update the question to use more exact language. For example, if you were only allowing alphanumeric then it's better to say "Assume keys take characters from [a-zA-Z0-9] and are non-empty" (Note, that example may allow too little but it's what I'm trying to explain.) – Captain Man Nov 8 at 15:47

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'

• fantastic, your answer beats @tsh, I am just curious what o.big is, could you please tell me about it? – chau giang Nov 8 at 8:24
• awesome, you rock! – chau giang Nov 8 at 8:33
• @tsh I've asked the OP if we need to support that kind of input. (Your solution has the same problem.) – Arnauld Nov 8 at 10:02

Ruby, 108146115 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!

Python 2, 122135122 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.

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!

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?


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

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.

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