web-dev-qa-db-fra.com

Parcourir tous les nœuds d'une arborescence d'objets JSON avec JavaScript

J'aimerais parcourir une arborescence d'objets JSON, mais je ne peux trouver aucune bibliothèque pour cela. Cela ne semble pas difficile mais on a l'impression de réinventer la roue.

En XML, il y a tellement de tutoriels montrant comment parcourir un arbre XML avec DOM :(

131
Takis Chan

Si vous pensez que jQuery est une sorte de overkill pour une tâche aussi primitive, vous pouvez faire quelque chose comme ça:

//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);
200
TheHippo

Un objet JSON est simplement un objet Javascript. C'est en fait ce que JSON signifie: JavaScript Object Notation. Donc, vous traverseriez un objet JSON, mais vous choisiriez de "traverser" un objet Javascript en général.

Dans ES2017, vous feriez:

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

Vous pouvez toujours écrire une fonction pour descendre récursivement dans l'objet:

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

Cela devrait être un bon point de départ. Je recommande fortement d'utiliser des méthodes javascript modernes pour de telles choses, car elles facilitent l'écriture de ce code.

56
Eli Courtwright
function traverse(o ) {
    for (i in o) {
        if (!!o[i] && typeof(o[i])=="object") {
            console.log(i, o[i])
            traverse(o[i] );
        }
    }
}
30
tejas

Il existe une nouvelle bibliothèque pour parcourir des données JSON avec JavaScript qui prend en charge de nombreux cas d'utilisation différents.

https://npmjs.org/package/traverse

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

Cela fonctionne avec toutes sortes d'objets JavaScript. Il détecte même les cycles.

Il fournit également le chemin de chaque nœud.

27
Benjamin Atkin

Cela dépend de ce que vous voulez faire. Voici un exemple de parcours d'une arborescence d'objets JavaScript, d'impression de clés et de valeurs au fur et à mesure:

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
12
Brian Campbell

Si vous parcourez un JSON string , vous pouvez utiliser une fonction 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)
})

En traversant un objet:

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)
})
7
David Lane

Je voulais utiliser la solution parfaite de @TheHippo dans une fonction anonyme, sans utiliser de processus ni de fonctions de déclenchement. Ce qui suit a fonctionné pour moi, partageant pour les programmeurs débutants comme moi. 

(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);
4
Raf

EDIT: cette réponse modifiée résout des parcours en boucle infinis.

Arrêter les traversées d'objets Pesky Inifinite

Une chose que je remarque n’a pas été résolue, c’est que vous devez être prudent en traversant des objets arbitraires (c'est-à-dire tout ensemble "aléatoire"), car les objets JavaScript peuvent être auto-référencés. Cela crée l’opportunité d’avoir des traversées en boucle infinies. Les données JSON non modifiées ne peuvent cependant pas être auto-référencées. Par conséquent, si vous utilisez ce sous-ensemble d'objets JS, vous n'avez pas à vous soucier des parcours en boucle infinie et vous pouvez vous référer à ma réponse d'origine ou à d'autres réponses. Voici un exemple de parcours sans fin (notez que ce n'est pas un morceau de code exécutable, car sinon, votre onglet navigateur planterait).

Pire - Cette boucle sera infinie sur les objets auto-référentiels:

//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. infinite loop)
o.o = o;

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

//that's all... no magic, no bloated framework
for(var [key, value] of traverse(o)) {
  console.log(key, value);
}

Pour vous en sauver, vous pouvez ajouter un ensemble dans une fermeture. Ainsi, lorsque la fonction est appelée pour la première fois, elle commence à créer une mémoire des objets qu'elle a vus et ne continue pas l'itération une fois qu'elle rencontre un objet déjà vu. L'extrait de code ci-dessous le fait et gère donc des cas de boucle infinis.

Mieux - Cela ne sera pas une boucle infinie sur les objets auto-référentiels:

//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. infinite loop)
o.o = o;

function* traverse(o,func) {
  const memory = new Set();
  function * innerTraversal (o, func) {
    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)) {
      yield [i,o[i]]; 
      if (o[i] !== null && typeof(o[i])=="object") {
        //going one step down in the object tree!!
        yield* innerTraversal(o[i],func);
      }
    }
  }
    
  yield* innerTraversal(o,func);
}
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value] of traverse(o)) {
  console.log(key, value);
}


Réponse originale

Pour une façon plus récente de le faire si cela ne vous dérange pas de laisser tomber IE et de supporter principalement les navigateurs plus récents (consultez le tableau es6 de kangax pour la compatibilité). Vous pouvez utiliser es2015 generators pour cela. J'ai mis à jour la réponse de @ TheHippo en conséquence. Bien sûr, si vous voulez vraiment IE - vous pouvez utiliser le transpiler babel JavaScript.

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

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

//that's all... no magic, no bloated framework
for(var [key, value] of traverse(o)) {
  console.log(key, value);
}

Si vous souhaitez uniquement posséder des propriétés énumérables (essentiellement des propriétés de chaîne non prototypes), vous pouvez le modifier en itérant à l'aide de Object.keys et d'une boucle for...of à la place:

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

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

//that's all... no magic, no bloated framework
for(var [key, value] of traverse(o)) {
  console.log(key, value);
}

3
John

La plupart des moteurs Javascript n'optimisent pas la récursion finale (cela peut ne pas être un problème si votre JSON n'est pas imbriqué en profondeur), mais je préfère généralement faire preuve de prudence et effectuer une itération, par exemple.

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)
2
Max

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>

0
seung

Mon script: 

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

JSON d'entrée:

[
    {
        "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": []
                    }
                ]
            }
       ]
   }
]

Appel de fonction:

callback_func(inp_json);

Sortie selon mon besoin:

["output/f1/ver"]
0
Mohideen ibn Mohammed