g | x | w | all
Bytes Lang Time Link
099PHP211003T161237ZLucianDe
256Go240614T142922Zbigyihsu
057Julia 1.0211006T162012ZMarcMush
092Ruby191108T030623ZValue In
075JavaScript Node.js191108T045052Ztsh
027Wolfram Language Mathematica191108T221506ZLegionMa
114R191108T173926ZNick Ken
072JavaScript V8191108T081953ZArnauld
159Red191108T091622ZGalen Iv
119Python 2191108T062742ZChas Bro

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);
    }
}

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}

Julia 1.0, 57 bytes

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

Try it online!

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!

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!

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

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.

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'

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

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.