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 :(
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 :(
Risposte:
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);
this
valore nella funzione target, mentre o
dovrebbe essere il primo parametro della funzione. Impostarlo su this
(che sarebbe la traverse
funzione) è un po 'strano, ma non è come process
usare this
comunque il riferimento. Potrebbe anche essere stato nullo.
/*jshint validthis: true */
sopra func.apply(this,[i,o[i]]);
per evitare l'errore W040: Possible strict violation.
causato dall'uso dithis
traverse
funzione che tiene traccia della profondità. Quando si chiama ricorsivamente, aggiungere 1 al livello corrente.
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.
function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
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]);
}
}
}
much better
?
!!o[i] && typeof o[i] == 'object'
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.
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
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)
})
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.
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 of
loop 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 break
uscire 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.keys
posto del for in
quale 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.keys
e for in
.
//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.
//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);
}
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.keys
e un for...of
ciclo 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);
}
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);
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)
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"]
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>
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 )
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:
findFirst(rootObj, 'children', { id: 1 })
per trovare il primo oggetto con id === 1
findAll(rootObj, 'children', { id: 1 })
per trovare tutti gli oggetti con id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 })
per eliminare il primo oggetto corrispondentefindAndDeleteAll(rootObj, 'children', { id: 1 })
per eliminare tutti gli oggetti corrispondentireplacementObj
viene utilizzato come ultimo parametro in due ultimi metodi:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
per cambiare il primo oggetto trovato con id === 1
il{ id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
per cambiare tutti gli oggetti con id === 1
in{ id: 2, name: 'newObj'}