Przeglądaj wszystkie węzły drzewa obiektów JSON za pomocą JavaScript

148

Chciałbym przejść przez drzewo obiektów JSON, ale nie mogę znaleźć do tego żadnej biblioteki. Nie wydaje się to trudne, ale wydaje się, że trzeba na nowo wynaleźć koło.

W XML jest tak wiele samouczków pokazujących, jak przejść przez drzewo XML z DOM :(

Patsy Issa
źródło
1
Stworzono iterator IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/ ... ma predefiniowaną (podstawową) DepthFirst & BreadthFirst next oraz możliwość poruszania się wewnątrz struktury JSON bez rekursji.
Tom

Odpowiedzi:

222

Jeśli uważasz, że jQuery to przesada jak na tak prymitywne zadanie, możesz zrobić coś takiego:

//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
źródło
2
Po co fundować. Aplikować (to, ...)? Czy nie powinno to być zabawne. Aplikować (o, ...)?
Craig Celeste
4
@ParchedSquid Nie. Jeśli spojrzysz na dokumentację API dotyczącą apply (), pierwszym parametrem jest thiswartość w funkcji docelowej, podczas gdy opowinien to być pierwszy parametr funkcji. Ustawienie go na this(która byłaby traversefunkcją) jest jednak trochę dziwne, ale i tak nie jest tak, że processużywa thisodwołania. Równie dobrze mógł być zerowy.
Thor84no
1
W przypadku jshint w trybie ścisłym może być konieczne dodanie /*jshint validthis: true */powyższego, func.apply(this,[i,o[i]]);aby uniknąć błędu W040: Possible strict violation.spowodowanego użyciemthis
Jasdeep Khalsa.
4
@jasdeepkhalsa: To prawda. Ale w momencie pisania odpowiedzi jshint nie był nawet początkiem projektu przez półtora roku.
TheHippo
1
@Vishal możesz dodać parametr 3 do traversefunkcji, która śledzi głębokość. Wywołanie Wenn rekurencyjnie dodaje 1 do bieżącego poziomu.
TheHippo
75

Obiekt JSON to po prostu obiekt JavaScript. To właśnie oznacza JSON: JavaScript Object Notation. Tak więc przechodziłbyś przez obiekt JSON, ale ogólnie wybrałbyś „przechodzenie” przez obiekt JavaScript.

W ES2017 zrobiłbyś:

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

Zawsze możesz napisać funkcję, która rekurencyjnie zejdzie do obiektu:

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

To powinien być dobry punkt wyjścia. Gorąco polecam używanie do tego celu nowoczesnych metod javascript, ponieważ znacznie ułatwiają pisanie takiego kodu.

Eli Courtwright
źródło
9
Unikaj trawersu (v), gdzie v == null, ponieważ (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim
4
Nienawidzę brzmieć pedantycznie, ale myślę, że jest już w tym wiele nieporozumień, więc dla jasności mówię, co następuje. Obiekty JSON i JavaScript to nie to samo. JSON jest oparty na formatowaniu obiektów JavaScript, ale JSON to tylko notacja ; jest to ciąg znaków reprezentujący obiekt. Wszystkie obiekty JSON można „przeanalizować” w obiekt JS, ale nie wszystkie obiekty JS można „przetworzyć” na format JSON. Na przykład obiekty JS odwołujące się do siebie nie mogą być traktowane łańcuchami.
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
źródło
6
Czy mógłbyś wyjaśnić, dlaczego tak jest much better?
Dementic
3
Jeśli metoda ma robić cokolwiek innego niż log, powinieneś sprawdzić, czy nie ma wartości null, null nadal jest obiektem.
wi1
3
@ wi1 Zgadzam się z tobą, można sprawdzić!!o[i] && typeof o[i] == 'object'
pilau
32

Jest nowa biblioteka do przechodzenia przez dane JSON za pomocą JavaScript, która obsługuje wiele różnych przypadków użycia.

https://npmjs.org/package/traverse

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

Działa ze wszystkimi rodzajami obiektów JavaScript. Wykrywa nawet cykle.

Zapewnia również ścieżkę do każdego węzła.

Benjamin Atkin
źródło
1
js-traverse wydaje się być również dostępny przez npm w node.js.
Ville
Tak. Nazywa się tam po prostu trawersem. I mają piękną stronę internetową! Aktualizuję odpowiedź, aby ją uwzględnić.
Benjamin Atkin
15

Zależy od tego, co chcesz zrobić. Oto przykład przechodzenia przez drzewo obiektów JavaScript, wypisywania kluczy i wartości na bieżąco:

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
źródło
9

Jeśli przechodzisz przez rzeczywisty ciąg JSON , możesz użyć funkcji ożywiania.

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

Podczas przechodzenia przez obiekt:

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
źródło
8

EDYCJA : Wszystkie poniższe przykłady w tej odpowiedzi zostały zmodyfikowane tak, aby zawierały nową zmienną ścieżki uzyskaną z iteratora zgodnie z żądaniem @ supersan . Zmienna ścieżki jest tablicą ciągów, w której każdy ciąg w tablicy reprezentuje każdy klucz, do którego uzyskano dostęp do wynikowej iterowanej wartości z oryginalnego obiektu źródłowego. Zmienna ścieżki może być wprowadzona do funkcji / metody get lodash . Lub możesz napisać własną wersję get lodash, która obsługuje tylko takie tablice:

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

EDYTOWAĆ : ta zmieniona odpowiedź rozwiązuje nieskończone pętle przechodzenia.

Zatrzymywanie nieznośnego przechodzenia przez nieskończone obiekty

Ta zredagowana odpowiedź nadal zapewnia jedną z dodatkowych zalet mojej oryginalnej odpowiedzi, która pozwala na użycie dostarczonej funkcji generatora w celu użycia czystszego i prostego iterowalnego interfejsu (pomyśl o użyciu for ofpętli tak, jak w for(var a of b)przypadku, gdy bjest iterowalny i ajest elementem iterowalnym ). Używając funkcji generatora, będąc prostszym interfejsem API, pomaga również w ponownym użyciu kodu, dzięki czemu nie musisz powtarzać logiki iteracji wszędzie tam, gdzie chcesz głęboko iterować właściwości obiektu, a także umożliwia breakwyjście z pętla, jeśli chcesz wcześniej przerwać iterację.

Zauważyłem, że jedna rzecz, która nie została poruszona i której nie ma w mojej pierwotnej odpowiedzi, to ostrożność podczas przechodzenia przez dowolne (tj. Dowolny "losowy" zestaw) obiektów, ponieważ obiekty JavaScript mogą odwoływać się do siebie. Stwarza to możliwość nieskończonej pętli przechodzenia. Niezmodyfikowane dane JSON nie mogą jednak odwoływać się do siebie, więc jeśli używasz tego szczególnego podzbioru obiektów JS, nie musisz się martwić o nieskończone pętle przechodzenia i możesz odwołać się do mojej oryginalnej odpowiedzi lub innych odpowiedzi. Oto przykład niekończącego się przejścia (pamiętaj, że nie jest to fragment kodu, który można uruchomić, ponieważ w przeciwnym razie spowodowałoby to awarię karty przeglądarki).

Również w obiekcie generatora w moim edytowanym przykładzie zdecydowałem się użyć, Object.keyszamiast for inktórego iteruje tylko klucze nieprototypowe na obiekcie. Możesz to zmienić samodzielnie, jeśli chcesz, aby zawierały klucze prototypowe. Zobacz moją oryginalną sekcję odpowiedzi poniżej dla obu implementacji z Object.keysifor in .

Gorzej - spowoduje to nieskończoną pętlę na obiektach odnoszących się do samych siebie:

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

Aby się przed tym uchronić, możesz dodać zestaw w zamknięciu, tak aby przy pierwszym wywołaniu funkcji zaczęła budować pamięć obiektów, które widziała i nie kontynuowała iteracji, gdy napotka już widziany obiekt. Poniższy fragment kodu robi to, a zatem obsługuje nieskończone przypadki zapętlenia.

Lepiej - to nie będzie nieskończonej pętli na obiektach odwołujących się do siebie:

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


Oryginalna odpowiedź

Aby uzyskać nowszy sposób, możesz to zrobić, jeśli nie masz nic przeciwko porzuceniu IE i głównie obsłudze nowszych przeglądarek (sprawdź tabelę es6 kangax pod kątem kompatybilności). Możesz użyć do tego generatorów es2015 . Odpowiednio zaktualizowałem odpowiedź @ TheHippo. Oczywiście, jeśli naprawdę chcesz obsługiwać IE, możesz użyć transpilera JavaScript babel .

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

Jeśli chcesz mieć tylko własne wyliczalne właściwości (w zasadzie nie-prototypowe właściwości łańcucha), możesz zmienić to na iterację przy użyciu Object.keysi for...ofzamiast tego pętli:

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

Jan
źródło
Świetna odpowiedź! Czy jest możliwe zwrócenie ścieżek, takich jak abc, abcd itp. Dla każdego przechodzonego klucza?
supersan
1
@supersan, możesz wyświetlić moje zaktualizowane fragmenty kodu. Dodałem zmienną ścieżki do każdej, która jest tablicą ciągów. Ciągi w tablicy reprezentują każdy klucz, do którego uzyskano dostęp do wynikowej iterowanej wartości z oryginalnego obiektu źródłowego.
Jan
4

Chciałem wykorzystać idealne rozwiązanie @TheHippo w funkcji anonimowej, bez użycia funkcji procesu i wyzwalacza. Poniższe działania zadziałały dla mnie, udostępnianie dla początkujących programistów, takich jak ja.

(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
źródło
2

Większość silników JavaScript nie optymalizuje rekurencji ogonowej (może to nie stanowić problemu, jeśli twój JSON nie jest głęboko zagnieżdżony), ale zwykle błądzę po stronie ostrożności i zamiast tego wykonuję iterację, np.

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)
Maks
źródło
0

Mój skrypt:

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

Wejście 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": []
                    }
                ]
            }
       ]
   }
]

Wywołanie funkcji:

callback_func(inp_json);

Wyjście zgodnie z moją potrzebą:

["output/f1/ver"]
Mohideen bin Mohammed
źródło
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
źródło
wysłał formularz enctype application / json
seung
-1

Najlepszym rozwiązaniem dla mnie było:

proste i bez użycia jakichkolwiek ram

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

Dzięki temu możesz uzyskać wszystkie klucze / wartości i zachować hierarchię

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

To jest modyfikacja na ( https://stackoverflow.com/a/25063574/1484447 )

Ricky
źródło
-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
źródło
-1

Stworzyłem bibliotekę do przechodzenia i edycji głęboko zagnieżdżonych obiektów JS. Sprawdź API tutaj: https://github.com/dominik791

Możesz także interaktywnie bawić się biblioteką za pomocą aplikacji demo: https://dominik791.github.io/obj-traverse-demo/

Przykłady użycia: Zawsze powinieneś mieć obiekt root, który jest pierwszym parametrem każdej metody:

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

Drugim parametrem jest zawsze nazwa właściwości przechowującej zagnieżdżone obiekty. W powyższym przypadku tak byłoby 'children'.

Trzeci parametr to obiekt, którego używasz do znajdowania obiektu / obiektów, które chcesz znaleźć / zmodyfikować / usunąć. Na przykład, jeśli szukasz obiektu o identyfikatorze równym 1, { id: 1}jako trzeci parametr podasz .

Możesz:

  1. findFirst(rootObj, 'children', { id: 1 }) znaleźć pierwszy obiekt za pomocą id === 1
  2. findAll(rootObj, 'children', { id: 1 }) aby znaleźć wszystkie obiekty za pomocą id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) aby usunąć pierwszy pasujący obiekt
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) aby usunąć wszystkie pasujące obiekty

replacementObj jest używany jako ostatni parametr w dwóch ostatnich metodach:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})aby zmienić pierwszy znaleziony obiekt za pomocą id === 1na{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})zmienić wszystkie obiekty za pomocą id === 1na{ id: 2, name: 'newObj'}
dominik791
źródło