Durchlaufen Sie alle Knoten eines JSON-Objektbaums mit JavaScript

148

Ich möchte einen JSON-Objektbaum durchlaufen, kann aber keine Bibliothek dafür finden. Es scheint nicht schwierig zu sein, aber es fühlt sich an, als würde man das Rad neu erfinden.

In XML gibt es so viele Tutorials, die zeigen, wie man einen XML-Baum mit DOM durchläuft :(

Patsy Issa
quelle
1
Iterator IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/… gemacht. Es hat (grundlegende) DepthFirst & BreadthFirst als nächstes vordefiniert und die Fähigkeit, sich ohne Rekursion innerhalb der JSON-Struktur zu bewegen.
Tom

Antworten:

222

Wenn Sie der Meinung sind, dass jQuery für eine so primitive Aufgabe eine Art Overkill ist , können Sie so etwas tun:

//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);
TheHippo
quelle
2
Warum fund.apply (dies, ...)? Sollte es nicht func.apply sein (o, ...)?
Craig Celeste
4
@ParchedSquid Nein. Wenn Sie sich die API-Dokumente für apply () ansehen, ist der erste Parameter der thisWert in der Zielfunktion, während oder erste Parameter für die Funktion sein sollte. Das Einstellen auf this(was die traverseFunktion wäre) ist zwar etwas seltsam, aber es ist sowieso nicht so, processals würde man die thisReferenz verwenden. Es hätte genauso gut null sein können.
Thor84no
1
Für jshint im strengen Modus müssen Sie möglicherweise /*jshint validthis: true */oben hinzufügen func.apply(this,[i,o[i]]);, um den Fehler zu vermeiden, der W040: Possible strict violation.durch die Verwendung vonthis
Jasdeep Khalsa
4
@ Jasdeepkhalsa: Das stimmt. Aber zum Zeitpunkt des Schreibens der Antwort war jshint noch nicht einmal anderthalb Jahre lang als Projekt gestartet.
TheHippo
1
@Vishal Sie können der traverseFunktion einen 3-Parameter hinzufügen, der die Tiefe verfolgt. Wenn Sie rekursiv aufrufen, addieren Sie 1 zur aktuellen Ebene.
TheHippo
74

Ein JSON-Objekt ist einfach ein Javascript-Objekt. Dafür steht JSON eigentlich: JavaScript Object Notation. Sie würden also ein JSON-Objekt durchlaufen, jedoch ein Javascript-Objekt im Allgemeinen "durchlaufen".

In ES2017 würden Sie Folgendes tun:

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

Sie können jederzeit eine Funktion schreiben, um rekursiv in das Objekt abzusteigen:

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

Dies sollte ein guter Ausgangspunkt sein. Ich empfehle dringend, für solche Dinge moderne Javascript-Methoden zu verwenden, da sie das Schreiben von Code erheblich erleichtern.

Eli Courtwright
quelle
9
Vermeiden Sie das Durchqueren von (v), wobei v == null ist, da (typeof null == "object") === true ist. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim
4
Ich mag es nicht, pedantisch zu klingen, aber ich denke, dass diesbezüglich bereits viel Verwirrung herrscht. Aus Gründen der Klarheit sage ich Folgendes. JSON- und JavaScript-Objekte sind nicht dasselbe. JSON basiert auf der Formatierung von JavaScript-Objekten, JSON ist jedoch nur die Notation . Es ist eine Zeichenfolge, die ein Objekt darstellt. Alle JSONs können in ein JS-Objekt "analysiert" werden, aber nicht alle JS-Objekte können in JSON "stringifiziert" werden. Beispielsweise können selbstreferenzielle JS-Objekte nicht stringifiziert werden.
John
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]);
        }
    }
}
Tejas
quelle
6
Können Sie erklären, warum es so ist much better?
Dementic
3
Wenn die Methode etwas anderes als log ausführen soll, sollten Sie nach null suchen, null ist immer noch ein Objekt.
wi1
3
@ wi1 Stimme dir zu, könnte nach!!o[i] && typeof o[i] == 'object'
Pilau
31

Es gibt eine neue Bibliothek zum Durchlaufen von JSON-Daten mit JavaScript, die viele verschiedene Anwendungsfälle unterstützt.

https://npmjs.org/package/traverse

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

Es funktioniert mit allen Arten von JavaScript-Objekten. Es erkennt sogar Zyklen.

Es gibt auch den Pfad jedes Knotens an.

Benjamin Atkin
quelle
1
js-traverse scheint auch über npm in node.js verfügbar zu sein.
Ville
Ja. Es heißt dort nur Traverse. Und sie haben eine schöne Webseite! Aktualisierung meiner Antwort, um sie aufzunehmen.
Benjamin Atkin
14

Kommt darauf an, was du machen willst. Hier ist ein Beispiel für das Durchlaufen eines JavaScript-Objektbaums, das Drucken von Schlüsseln und Werten währenddessen:

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
Brian Campbell
quelle
8

Wenn Sie eine tatsächliche JSON- Zeichenfolge durchlaufen , können Sie eine Reviver-Funktion verwenden.

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

Beim Durchqueren eines Objekts:

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

BEARBEITEN : Alle folgenden Beispiele in dieser Antwort wurden so bearbeitet, dass sie eine neue Pfadvariable enthalten, die vom Iterator gemäß der Anforderung von @ supersan ausgegeben wird . Die Pfadvariable ist ein Array von Zeichenfolgen, wobei jede Zeichenfolge im Array jeden Schlüssel darstellt, auf den zugegriffen wurde, um zum resultierenden iterierten Wert aus dem ursprünglichen Quellobjekt zu gelangen. Die Pfadvariable kann in die get-Funktion / -Methode von lodash eingespeist werden . Oder Sie könnten Ihre eigene Version von lodashs get schreiben, die nur Arrays wie folgt verarbeitet:

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"]));

BEARBEITEN : Diese bearbeitete Antwort löst Endlosschleifen-Durchquerungen.

Stoppen von lästigen unendlichen Objektdurchquerungen

Diese bearbeitete Antwort bietet immer noch einen der zusätzlichen Vorteile meiner ursprünglichen Antwort, mit der Sie die bereitgestellte Generatorfunktion verwenden können, um eine sauberere und einfach iterierbare Schnittstelle zu verwenden (denken Sie an die Verwendung von for ofSchleifen, bei for(var a of b)denen bes sich um eine iterierbare und aein Element der iterierbaren handelt ). Durch die Verwendung hilft es auch , mit Wiederverwendung von Code die Generatorfunktion zusammen mit einer einfacheren api zu sein , indem sie es machen , so dass Sie müssen nicht die Iterationslogik wiederholen überall Sie Iterierte wollen tief auf die Eigenschaften eines Objekts und macht es auch möglich , sie breakaus die Schleife, wenn Sie die Iteration früher stoppen möchten.

Eine Sache, die mir auffällt und die nicht in meiner ursprünglichen Antwort enthalten ist, ist, dass Sie vorsichtig beliebige (dh "zufällige") Objekte durchlaufen sollten, da JavaScript-Objekte selbstreferenzierend sein können. Dies schafft die Möglichkeit, Endlosschleifen-Durchquerungen durchzuführen. Nicht geänderte JSON-Daten können sich jedoch nicht selbst referenzieren. Wenn Sie also diese bestimmte Teilmenge von JS-Objekten verwenden, müssen Sie sich keine Gedanken über Endlosschleifen-Durchläufe machen, und Sie können auf meine ursprüngliche Antwort oder andere Antworten verweisen. Hier ist ein Beispiel für eine nicht endende Durchquerung (beachten Sie, dass es sich nicht um einen ausführbaren Code handelt, da sonst die Registerkarte Ihres Browsers abstürzen würde).

Auch im Generatorobjekt in meinem bearbeiteten Beispiel habe ich mich für die Verwendung entschieden, Object.keysanstatt for innur Nicht-Prototyp-Schlüssel für das Objekt zu iterieren. Sie können dies selbst austauschen, wenn Sie die Prototypschlüssel enthalten möchten. Siehe meinen ursprünglichen Antwortabschnitt unten für beide Implementierungen mit Object.keysund for in.

Schlimmer - Dies führt zu einer Endlosschleife für selbstreferenzielle Objekte:

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

Um sich davor zu schützen, können Sie einen Satz innerhalb eines Abschlusses hinzufügen, sodass die Funktion beim ersten Aufruf beginnt, einen Speicher für die Objekte zu erstellen, die sie gesehen hat, und die Iteration nicht fortsetzt, sobald sie auf ein bereits gesehenes Objekt stößt. Das folgende Code-Snippet macht das und behandelt somit Endlosschleifenfälle.

Besser - Dies führt nicht zu einer Endlosschleife für selbstreferenzielle Objekte:

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


Ursprüngliche Antwort

Eine neuere Möglichkeit, dies zu tun, wenn es Ihnen nichts ausmacht, den IE zu löschen und hauptsächlich aktuellere Browser zu unterstützen (überprüfen Sie die es6-Tabelle von kangax auf Kompatibilität). Hierfür können Sie es2015- Generatoren verwenden . Ich habe die Antwort von @ TheHippo entsprechend aktualisiert. Wenn Sie wirklich IE-Unterstützung wünschen, können Sie natürlich den babel JavaScript-Transpiler verwenden.

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

Wenn Sie nur eigene aufzählbare Eigenschaften wünschen (im Grunde keine Eigenschaften einer Prototypkette), können Sie diese so ändern, dass sie stattdessen mit Object.keysund einer for...ofSchleife iteriert werden :

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

John
quelle
Gute Antwort! Ist es möglich, Pfade wie abc, abcd usw. für jeden Schlüssel zurückzugeben, der durchlaufen wird?
Supersan
1
@supersan Sie können meine aktualisierten Code-Schnipsel anzeigen. Ich habe jeder Pfadvariablen eine Pfadvariable hinzugefügt, die ein Array von Zeichenfolgen ist. Die Zeichenfolgen im Array stellen jeden Schlüssel dar, auf den zugegriffen wurde, um zum resultierenden iterierten Wert aus dem ursprünglichen Quellobjekt zu gelangen.
John
4

Ich wollte die perfekte Lösung von @TheHippo in einer anonymen Funktion verwenden, ohne Prozess- und Triggerfunktionen zu verwenden. Das Folgende hat für mich funktioniert und für unerfahrene Programmierer wie mich geteilt.

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

Die meisten Javascript-Engines optimieren die Schwanzrekursion nicht (dies ist möglicherweise kein Problem, wenn Ihr JSON nicht tief verschachtelt ist), aber ich bin normalerweise vorsichtig und mache stattdessen eine Iteration, z

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)
Max
quelle
0

Mein Skript:

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

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

Funktionsaufruf:

callback_func(inp_json);

Ausgabe gemäß meinem Bedarf:

["output/f1/ver"]
Mohideen bin Mohammed
quelle
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>

seung
quelle
machte es zu senden Formular enctype applyatioin / json
seung
-1

Die beste Lösung für mich war die folgende:

einfach und ohne Verwendung eines Frameworks

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

Sie können alle Schlüssel / Werte abrufen und damit die Hierarchie beibehalten

// 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')

Dies ist eine Änderung am ( https://stackoverflow.com/a/25063574/1484447 )

Ricky
quelle
-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>);
                 }
RAJ KADAM
quelle
-1

Ich habe eine Bibliothek erstellt, um tief verschachtelte JS-Objekte zu durchlaufen und zu bearbeiten. Überprüfen Sie die API hier: https://github.com/dominik791

Sie können auch interaktiv mit der Bibliothek spielen, indem Sie die Demo-App verwenden: https://dominik791.github.io/obj-traverse-demo/

Anwendungsbeispiele: Sie sollten immer ein Root-Objekt haben, das der erste Parameter jeder Methode ist:

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

Der zweite Parameter ist immer der Name der Eigenschaft, die verschachtelte Objekte enthält. Im obigen Fall wäre es 'children'.

Der dritte Parameter ist ein Objekt, mit dem Sie Objekte finden, die Sie suchen / ändern / löschen möchten. Wenn Sie beispielsweise nach einem Objekt mit einer ID von 1 suchen, übergeben Sie es { id: 1}als dritten Parameter.

Und du kannst:

  1. findFirst(rootObj, 'children', { id: 1 }) erstes Objekt finden mit id === 1
  2. findAll(rootObj, 'children', { id: 1 }) um alle Objekte mit zu finden id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) um das erste übereinstimmende Objekt zu löschen
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) um alle übereinstimmenden Objekte zu löschen

replacementObj wird als letzter Parameter in zwei letzten Methoden verwendet:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})um das zuerst gefundene Objekt mit id === 1dem zu ändern{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})um alle Objekte mit id === 1dem zu ändern{ id: 2, name: 'newObj'}
dominik791
quelle