Attraversa tutti i nodi di un albero di oggetti JSON con JavaScript


148

Vorrei attraversare un albero di oggetti JSON, ma non riesco a trovare alcuna libreria per quello. Non sembra difficile ma sembra reinventare la ruota.

In XML ci sono così tanti tutorial che mostrano come attraversare un albero XML con DOM :(


1
Realizzato iteratore IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/… ha DepthFirst & BreadthFirst predefiniti (prima) e prima possibilità e possibilità di muoversi all'interno della struttura JSON senza ricorsione.
Tom,

Risposte:


222

Se pensi che jQuery sia un po ' eccessivo per un compito così primitivo, potresti fare qualcosa del genere:

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

//called with every property and its value
function process(key,value) {
    console.log(key + " : "+value);
}

function traverse(o,func) {
    for (var i in o) {
        func.apply(this,[i,o[i]]);  
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            traverse(o[i],func);
        }
    }
}

//that's all... no magic, no bloated framework
traverse(o,process);

2
Perché fund.apply (questo, ...)? Non dovrebbe essere func.apply (o, ...)?
Craig Celeste,

4
@ParchedSquid No. Se si guardano i documenti API per apply () il primo parametro è il thisvalore nella funzione target, mentre odovrebbe essere il primo parametro della funzione. Impostarlo su this(che sarebbe la traversefunzione) è un po 'strano, ma non è come processusare thiscomunque il riferimento. Potrebbe anche essere stato nullo.
Thor84no,

1
Per jshint in modalità rigorosa potrebbe essere necessario aggiungere /*jshint validthis: true */sopra func.apply(this,[i,o[i]]);per evitare l'errore W040: Possible strict violation.causato dall'uso dithis
Jasdeep Khalsa

4
@jasdeepkhalsa: È vero. Ma al momento della stesura della risposta jshint non era nemmeno iniziato come progetto per un anno e mezzo.
TheHippo

1
@Vishal è possibile aggiungere un parametro 3 alla traversefunzione che tiene traccia della profondità. Quando si chiama ricorsivamente, aggiungere 1 al livello corrente.
TheHippo,

75

Un oggetto JSON è semplicemente un oggetto Javascript. Questo è ciò che rappresenta JSON: JavaScript Object Notation. Quindi attraverseresti un oggetto JSON ma sceglieresti di "attraversare" un oggetto Javascript in generale.

In ES2017 faresti:

Object.entries(jsonObj).forEach(([key, value]) => {
    // do something with key and val
});

Puoi sempre scrivere una funzione per scendere ricorsivamente nell'oggetto:

function traverse(jsonObj) {
    if( jsonObj !== null && typeof jsonObj == "object" ) {
        Object.entries(jsonObj).forEach(([key, value]) => {
            // key is either an array index or object key
            traverse(value);
        });
    }
    else {
        // jsonObj is a number or string
    }
}

Questo dovrebbe essere un buon punto di partenza. Consiglio vivamente di utilizzare i moderni metodi javascript per tali cose, poiché rendono molto più semplice la scrittura di tale codice.


9
Evita l'attraversamento (v) dove v == null, perché (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim,

4
Odio sembrare pedante, ma penso che ci sia già molta confusione su questo, quindi solo per motivi di chiarezza dico quanto segue. Gli oggetti JSON e JavaScript non sono la stessa cosa. JSON si basa sulla formattazione di oggetti JavaScript, ma JSON è solo la notazione ; è una stringa di caratteri che rappresentano un oggetto. Tutti gli JSON possono essere "analizzati" in un oggetto JS, ma non tutti gli oggetti JS possono essere "stringiti" in JSON. Ad esempio, gli oggetti JS autoreferenziali non possono essere stringiti.
Giovanni,

36
function traverse(o) {
    for (var i in o) {
        if (!!o[i] && typeof(o[i])=="object") {
            console.log(i, o[i]);
            traverse(o[i]);
        } else {
            console.log(i, o[i]);
        }
    }
}

6
Potresti spiegare perché much better?
Dementic,

3
Se si intende che il metodo esegua operazioni diverse dal registro, è necessario verificare la presenza di null, null è comunque un oggetto.
1

3
@ wi1 D'accordo con te, potrebbe verificare!!o[i] && typeof o[i] == 'object'
pilau

32

C'è una nuova libreria per attraversare i dati JSON con JavaScript che supporta molti casi d'uso diversi.

https://npmjs.org/package/traverse

https://github.com/substack/js-traverse

Funziona con tutti i tipi di oggetti JavaScript. Rileva persino i cicli.

Fornisce anche il percorso di ciascun nodo.


1
js-traverse sembra essere disponibile anche via npm in node.js.
Ville,

Sì. Si chiama solo attraversare lì. E hanno una bella pagina web! Aggiornamento della mia risposta per includerla.
Benjamin Atkin,

15

Dipende da cosa vuoi fare. Ecco un esempio di attraversamento di un albero di oggetti JavaScript, stampa di chiavi e valori mentre procede:

function js_traverse(o) {
    var type = typeof o 
    if (type == "object") {
        for (var key in o) {
            print("key: ", key)
            js_traverse(o[key])
        }
    } else {
        print(o)
    }
}

js> foobar = {foo: "bar", baz: "quux", zot: [1, 2, 3, {some: "hash"}]}
[object Object]
js> js_traverse(foobar)                 
key:  foo
bar
key:  baz
quux
key:  zot
key:  0
1
key:  1
2
key:  2
3
key:  3
key:  some
hash

9

Se stai attraversando una stringa JSON effettiva, puoi utilizzare una funzione reviver.

function traverse (json, callback) {
  JSON.parse(json, function (key, value) {
    if (key !== '') {
      callback.call(this, key, value)
    }
    return value
  })
}

traverse('{"a":{"b":{"c":{"d":1}},"e":{"f":2}}}', function (key, value) {
  console.log(arguments)
})

Quando si attraversa un oggetto:

function traverse (obj, callback, trail) {
  trail = trail || []

  Object.keys(obj).forEach(function (key) {
    var value = obj[key]

    if (Object.getPrototypeOf(value) === Object.prototype) {
      traverse(value, callback, trail.concat(key))
    } else {
      callback.call(obj, key, value, trail)
    }
  })
}

traverse({a: {b: {c: {d: 1}}, e: {f: 2}}}, function (key, value, trail) {
  console.log(arguments)
})

8

EDIT : tutti gli esempi seguenti in questa risposta sono stati modificati per includere una nuova variabile di percorso prodotta dall'iteratore come richiesto da @ supersan . La variabile path è una matrice di stringhe in cui ogni stringa nella matrice rappresenta ciascuna chiave a cui è stato effettuato l'accesso per ottenere il valore iterato risultante dall'oggetto di origine originale. La variabile path può essere inserita nella funzione / metodo get di lodash . Oppure potresti scrivere la tua versione di lodash's get che gestisce solo array in questo modo:

function get (object, path) {
  return path.reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object);
}

const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(get(example, ["a", "0"]));
console.log(get(example, ["c", "d", "0"]));
console.log(get(example, ["b"]));
// these paths do not exist on the object
console.log(get(example, ["e", "f", "g"]));
console.log(get(example, ["b", "f", "g"]));

EDIT : questa risposta modificata risolve infiniti spostamenti in loop.

Arresto di traversate di oggetti infiniti fastidiosi

Questa risposta modificata fornisce ancora uno dei vantaggi aggiuntivi della mia risposta originale che ti consente di utilizzare la funzione di generatore fornita al fine di utilizzare un'interfaccia iterabile più pulita e semplice (pensa a usare i for ofloop come in for(var a of b)dove bè un iterabile ed aè un elemento dell'iterabile ). Usando la funzione del generatore oltre ad essere un'API più semplice aiuta anche con il riutilizzo del codice rendendolo così che non devi ripetere la logica di iterazione ovunque tu voglia iterare profondamente sulle proprietà di un oggetto e rende anche possibile breakuscire da il ciclo se si desidera interrompere prima l'iterazione.

Una cosa che noto che non è stata affrontata e che non è nella mia risposta originale è che dovresti stare attento a attraversare oggetti arbitrari (cioè qualsiasi insieme "casuale" di), perché gli oggetti JavaScript possono essere autoreferenziali. Ciò crea l'opportunità di avere infiniti attraversamenti a ciclo continuo. I dati JSON non modificati, tuttavia, non possono essere autoreferenziali, quindi se stai utilizzando questo particolare sottoinsieme di oggetti JS non devi preoccuparti di infiniti spostamenti in loop e puoi fare riferimento alla mia risposta originale o ad altre risposte. Ecco un esempio di un attraversamento senza fine (nota che non è un pezzo di codice eseguibile, perché altrimenti si bloccherebbe la scheda del browser).

Anche nell'oggetto generatore nel mio esempio modificato ho optato per l'uso al Object.keysposto del for inquale scorre solo le chiavi non prototipo sull'oggetto. Puoi scambiarlo da solo se vuoi includere le chiavi del prototipo. Vedere la mia risposta sezione originale di seguito per entrambe le implementazioni con Object.keyse for in.

Peggio ancora: questo ciclo infinito su oggetti autoreferenziali:

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

// this self-referential property assignment is the only edited line 
// from the below original example which makes the traversal 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

function* traverse(o, path=[]) {
    for (var i of Object.keys(o)) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath]; 
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[I], itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}

Per salvarti da questo puoi aggiungere un set all'interno di una chiusura, in modo che quando la funzione viene chiamata per la prima volta inizia a costruire una memoria degli oggetti che ha visto e non continua l'iterazione una volta che incontra un oggetto già visto. Lo snippet di codice seguente lo fa e quindi gestisce infiniti casi di loop.

Meglio: questo non eseguirà un ciclo infinito su oggetti autoreferenziali:

//your object
var o = { 
  foo:"bar",
  arr:[1,2,3],
  subo: {
    foo2:"bar2"
  }
};

// this self-referential property assignment is the only edited line 
// from the below original example which makes more naive traversals 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

function* traverse(o) {
  const memory = new Set();
  function * innerTraversal (o, path=[]) {
    if(memory.has(o)) {
      // we've seen this object before don't iterate it
      return;
    }
    // add the new object to our memory.
    memory.add(o);
    for (var i of Object.keys(o)) {
      const itemPath = path.concat(i);
      yield [i,o[i],itemPath]; 
      if (o[i] !== null && typeof(o[i])=="object") {
        //going one step down in the object tree!!
        yield* innerTraversal(o[i], itemPath);
      }
    }
  }
    
  yield* innerTraversal(o);
}
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}


Risposta originale

Per un modo più nuovo di farlo se non ti dispiace far cadere IE e supportare principalmente più browser attuali (controlla la tabella es6 di kangax per la compatibilità). Per questo puoi usare i generatori es2015 . Ho aggiornato la risposta di @ TheHippo di conseguenza. Ovviamente se vuoi davvero il supporto IE puoi usare il transpiler di babel JavaScript.

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

function* traverse(o, path=[]) {
    for (var i in o) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath];
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[i], itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}

Se si desidera solo le proprietà enumerabili proprie (in sostanza proprietà della catena non prototipo), è possibile modificarlo per iterare utilizzando Object.keyse un for...ofciclo invece:

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

function* traverse(o,path=[]) {
    for (var i of Object.keys(o)) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath];
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[i],itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}


Bella risposta! È possibile restituire percorsi come abc, abcd, ecc. Per ogni chiave che viene attraversata?
Sostituisce

1
@supersan puoi visualizzare i miei frammenti di codice aggiornati. Ho aggiunto una variabile di percorso a ciascuna che è una matrice di stringhe. Le stringhe nell'array rappresentano ciascuna chiave a cui è stato effettuato l'accesso per ottenere il valore iterato risultante dall'oggetto di origine originale.
Giovanni,

4

Volevo usare la soluzione perfetta di @TheHippo in una funzione anonima, senza usare le funzioni di processo e trigger. Quanto segue ha funzionato per me, condividendolo con programmatori principianti come me.

(function traverse(o) {
    for (var i in o) {
        console.log('key : ' + i + ', value: ' + o[i]);

        if (o[i] !== null && typeof(o[i])=="object") {
            //going on step down in the object tree!!
            traverse(o[i]);
        }
    }
  })
  (json);

2

La maggior parte dei motori Javascript non ottimizza la ricorsione della coda (questo potrebbe non essere un problema se il tuo JSON non è profondamente annidato), ma di solito sbaglio sul lato della cautela e faccio invece iterazione, ad es.

function traverse(o, fn) {
    const stack = [o]

    while (stack.length) {
        const obj = stack.shift()

        Object.keys(obj).forEach((key) => {
            fn(key, obj[key], obj)
            if (obj[key] instanceof Object) {
                stack.unshift(obj[key])
                return
            }
        })
    }
}

const o = {
    name: 'Max',
    legal: false,
    other: {
        name: 'Maxwell',
        nested: {
            legal: true
        }
    }
}

const fx = (key, value, obj) => console.log(key, value)
traverse(o, fx)

0

La mia sceneggiatura:

op_needed = [];
callback_func = function(val) {
  var i, j, len;
  results = [];
  for (j = 0, len = val.length; j < len; j++) {
    i = val[j];
    if (i['children'].length !== 0) {
      call_func(i['children']);
    } else {
      op_needed.push(i['rel_path']);
    }
  }
  return op_needed;
};

Input JSON:

[
    {
        "id": null, 
        "name": "output",   
        "asset_type_assoc": [], 
        "rel_path": "output",
        "children": [
            {
                "id": null, 
                "name": "output",   
                "asset_type_assoc": [], 
                "rel_path": "output/f1",
                "children": [
                    {
                        "id": null, 
                        "name": "v#",
                        "asset_type_assoc": [], 
                        "rel_path": "output/f1/ver",
                        "children": []
                    }
                ]
            }
       ]
   }
]

Chiamata di funzione:

callback_func(inp_json);

Uscita secondo il mio bisogno:

["output/f1/ver"]

0

var test = {
    depth00: {
        depth10: 'string'
        , depth11: 11
        , depth12: {
            depth20:'string'
            , depth21:21
        }
        , depth13: [
            {
                depth22:'2201'
                , depth23:'2301'
            }
            , {
                depth22:'2202'
                , depth23:'2302'
            }
        ]
    }
    ,depth01: {
        depth10: 'string'
        , depth11: 11
        , depth12: {
            depth20:'string'
            , depth21:21
        }
        , depth13: [
            {
                depth22:'2201'
                , depth23:'2301'
            }
            , {
                depth22:'2202'
                , depth23:'2302'
            }
        ]
    }
    , depth02: 'string'
    , dpeth03: 3
};


function traverse(result, obj, preKey) {
    if(!obj) return [];
    if (typeof obj == 'object') {
        for(var key in obj) {
            traverse(result, obj[key], (preKey || '') + (preKey ? '[' +  key + ']' : key))
        }
    } else {
        result.push({
            key: (preKey || '')
            , val: obj
        });
    }
    return result;
}

document.getElementById('textarea').value = JSON.stringify(traverse([], test), null, 2);
<textarea style="width:100%;height:600px;" id="textarea"></textarea>


lo fece per inviare il modulo enctype applicatioin / json
visto il

-1

La migliore soluzione per me è stata la seguente:

semplice e senza usare alcun framework

    var doSomethingForAll = function (arg) {
       if (arg != undefined && arg.length > 0) {
            arg.map(function (item) {
                  // do something for item
                  doSomethingForAll (item.subitem)
             });
        }
     }

-1

Puoi ottenere tutte le chiavi / valori e preservare la gerarchia con questo

// get keys of an object or array
function getkeys(z){
  var out=[]; 
  for(var i in z){out.push(i)};
  return out;
}

// print all inside an object
function allInternalObjs(data, name) {
  name = name || 'data';
  return getkeys(data).reduce(function(olist, k){
    var v = data[k];
    if(typeof v === 'object') { olist.push.apply(olist, allInternalObjs(v, name + '.' + k)); }
    else { olist.push(name + '.' + k + ' = ' + v); }
    return olist;
  }, []);
}

// run with this
allInternalObjs({'a':[{'b':'c'},{'d':{'e':5}}],'f':{'g':'h'}}, 'ob')

Questa è una modifica su ( https://stackoverflow.com/a/25063574/1484447 )


-1
             var localdata = [{''}]// Your json array
              for (var j = 0; j < localdata.length; j++) 
               {$(localdata).each(function(index,item)
                {
                 $('#tbl').append('<tr><td>' + item.FirstName +'</td></tr>);
                 }

-1

Ho creato una libreria per attraversare e modificare oggetti JS nidificati in profondità. Dai un'occhiata all'API qui: https://github.com/dominik791

Puoi anche giocare con la libreria in modo interattivo utilizzando l'app demo: https://dominik791.github.io/obj-traverse-demo/

Esempi di utilizzo: dovresti sempre avere l'oggetto root che è il primo parametro di ogni metodo:

var rootObj = {
  name: 'rootObject',
  children: [
    {
      'name': 'child1',
       children: [ ... ]
    },
    {
       'name': 'child2',
       children: [ ... ]
    }
  ]
};

Il secondo parametro è sempre il nome della proprietà che contiene oggetti nidificati. Nel caso sopra sarebbe 'children'.

Il terzo parametro è un oggetto che si utilizza per trovare oggetti / oggetti che si desidera trovare / modificare / eliminare. Ad esempio, se stai cercando un oggetto con ID uguale a 1, passerai { id: 1}come terzo parametro.

E tu puoi:

  1. findFirst(rootObj, 'children', { id: 1 }) per trovare il primo oggetto con id === 1
  2. findAll(rootObj, 'children', { id: 1 }) per trovare tutti gli oggetti con id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) per eliminare il primo oggetto corrispondente
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) per eliminare tutti gli oggetti corrispondenti

replacementObj viene utilizzato come ultimo parametro in due ultimi metodi:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})per cambiare il primo oggetto trovato con id === 1il{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})per cambiare tutti gli oggetti con id === 1in{ id: 2, name: 'newObj'}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.