JavaScript ile JSON Nesne Ağacının Tüm Düğümlerinde Gezinin


148

Bir JSON nesne ağacında gezinmek istiyorum, ancak bunun için herhangi bir kütüphane bulamıyorum. Zor görünmüyor ama tekerleği yeniden icat etmek gibi geliyor.

XML'de DOM ile bir XML ağacında nasıl gezinileceğini gösteren çok sayıda öğretici var :(


1
Yineleyici IIFE yaptı github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/… önceden tanımlanmış (temel) DepthFirst & BreadthFirst ve tekrarlama olmadan JSON yapısının içinde hareket etme yeteneğine sahiptir.
Tom

Yanıtlar:


222

Eğer jQuery'nin bu tür ilkel bir görev için aşırı derecede dolgun olduğunu düşünüyorsanız, böyle bir şey yapabilirsiniz:

//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
Neden fund.apply (bu, ...)? İşlevi olmamalı mı? Uygula (o, ...)?
Craig Celeste

4
Uygula () için API belgelerine bakarsanız , ilk parametre thishedef işlevdeki değerdir, oysa işlevin oilk parametresi olmalıdır. this(Bu traverseişlev olurdu) ayarlamak biraz garip, ama yine processde thisreferans kullanır gibi değil . Bu da boş olabilirdi.
Thor84no

1
Sıkı modda jshint için olsa da kullanımından kaynaklanan hatayı önlemek için /*jshint validthis: true */yukarıda eklemeniz gerekebilirfunc.apply(this,[i,o[i]]);W040: Possible strict violation.this
Jasdeep Khalsa

4
@jasdeepkhalsa: Bu doğru. Ancak cevabın yazıldığı sırada jshint bir buçuk yıl boyunca bir proje olarak bile başlatılmadı.
TheHippo

1
@Vishal traverse, derinliği izleyen işleve 3 parametre ekleyebilirsiniz . Wenn çağrısı tekrar tekrar mevcut seviyeye 1 ekleyin.
TheHippo

75

Bir JSON nesnesi basitçe bir Javascript nesnesidir. Aslında JSON şu anlama gelir: JavaScript Object Notation. Böylece bir JSON nesnesini geçersiniz, ancak genel olarak bir Javascript nesnesini "geçmeyi" tercih edersiniz.

ES2017'de şunları yaparsınız:

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

Nesneye yinelemeli olarak inmek için her zaman bir işlev yazabilirsiniz:

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

Bu iyi bir başlangıç ​​noktası olmalı. Bu tür kodları yazmayı çok daha kolay hale getirdiğinden, bu tür şeyler için modern javascript yöntemlerini kullanmanızı şiddetle tavsiye ederim.


9
(V) yönünden kaçının (v) burada n = null, çünkü (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim

4
Bilgiçlik söylemekten nefret ediyorum, ama bence bu konuda çok fazla kafa karışıklığı var, bu yüzden sadece netlik uğruna şunları söylüyorum. JSON ve JavaScript Nesneleri aynı şey değildir. JSON, JavaScript nesnelerinin biçimlendirmesine dayanır, ancak JSON yalnızca gösterimdir ; bir nesneyi temsil eden bir karakter dizisidir. Tüm JSON bir JS nesnesine "ayrıştırılabilir", ancak tüm JS nesneleri JSON'a "dizgilenemez". Örneğin, kendinden referanslı JS nesneleri dizilemez.
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]);
        }
    }
}

6
Neden olduğunu açıklayabilir misiniz much better?
Dementic

3
Yöntemin log dışında bir şey yapması gerekiyorsa null olup olmadığını kontrol etmeniz gerekir, null yine de bir nesnedir.
wi1

3
@ wi1 Seninle aynı fikirde, kontrol edebilir!!o[i] && typeof o[i] == 'object'
pilau

32

JSON verilerini JavaScript ile taramak için birçok farklı kullanım durumunu destekleyen yeni bir kütüphane var.

https://npmjs.org/package/traverse

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

Her türlü JavaScript nesnesiyle çalışır. Döngüleri bile algılar.

Her düğümün yolunu da sağlar.


1
js-traverse, node.js'de npm aracılığıyla da kullanılabilir gibi görünüyor.
Ville

Evet. Buna sadece travers deniyor. Ve çok güzel bir web sayfaları var! Cevabımı ekleyecek şekilde güncelleniyor.
Benjamin Atkin

15

Ne yapmak istediğine bağlı. Aşağıda, bir JavaScript nesne ağacında gezinme, anahtarları ve değerleri gittikçe yazdırma örneği:

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

Gerçek bir JSON dizesini geziyorsanız, bir reviver işlevi kullanabilirsiniz.

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

Bir nesneyi gezerken:

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

DÜZENLEME : Bu yanıttaki aşağıdaki tüm örnekler @ supersan'ın isteğine göre yineleyiciden gelen yeni bir yol değişkeni içerecek şekilde düzenlenmiştir . Path değişkeni, dizideki her dizenin orijinal kaynak nesneden elde edilen yinelenen değere ulaşmak için erişilen her anahtarı temsil ettiği bir dizeler dizisidir. Yol değişkeni, lodash'ın get işlevine / yöntemine beslenebilir . Ya da sadece dizileri işleyen lodash's get'in kendi versiyonunuzu yazabilirsiniz:

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 : Bu düzenlenmiş cevap sonsuz döngü geçişleri çözer.

Pesky Sonsuz Nesne Geçişlerini Durdurma

Bu Düzenlenen cevap hala sağlanan kullanmanızı sağlar orijinal cevap eklenen faydaları birini sağlayan jeneratör işlevi daha temiz ve basit kullanmak için iterable arabirimini (kullanarak düşünmek for ofgibi döngüler for(var a of b)nerede bbir iterable ve aiterable bir unsurdur ). Bunu yaparak da kod yeniden yardımcı olur basit bir api olmanın yanı sıra jeneratör fonksiyonu kullanarak size derinden bir nesnenin özelliklerine yineleme istediğiniz her yerde yineleme mantığı tekrarlamak zorunda kalmamak için ve aynı zamanda mümkün kılar breakdışı daha önce yinelemeyi durdurmak istiyorsanız döngü.

Hitap etmediğim ve orijinal cevabımda olmayan fark ettiğim bir şey, JavaScript nesneleri kendinden referanslı olabileceğinden, rastgele (yani herhangi bir "rastgele" küme) nesneden geçmeye dikkat etmeniz gerektiğidir. Bu sonsuz döngüsel geçişlere sahip olma fırsatı yaratır. Ancak değiştirilmemiş JSON verileri kendi kendine referans olamaz, bu nedenle JS nesnelerinin bu özel alt kümesini kullanıyorsanız sonsuz döngü geçişleri hakkında endişelenmenize gerek yoktur ve orijinal cevabımı veya diğer cevaplarıma başvurabilirsiniz. İşte bitmeyen bir geçişe bir örnek (çalıştırılabilir bir kod parçası olmadığına dikkat edin, aksi takdirde tarayıcı sekmenizi kilitler).

Ayrıca benim düzenlenmiş örnekte jeneratör nesnesinde ben sadece nesne üzerinde prototip olmayan anahtarları yinelemek Object.keysyerine kullanmayı tercih etti for in. Prototip anahtarlarının dahil edilmesini istiyorsanız bunu kendiniz değiştirebilirsiniz. Object.keysVe ile yapılan her iki uygulama için aşağıdaki orijinal yanıt bölümüme bakın for in.

Daha da kötüsü - Bu, kendinden referanslı nesneler üzerinde sonsuz döngü oluşturur:

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

Kendinizi bundan kurtarmak için, bir kapatma içine bir küme ekleyebilirsiniz, böylece işlev ilk çağrıldığında, gördüğü nesnelerin bir belleğini oluşturmaya başlar ve zaten görülen bir nesneye rastladığında yinelemeye devam etmez. Aşağıdaki kod snippet'i bunu yapar ve böylece sonsuz döngü durumlarını işler.

Daha İyi - Kendine referans veren nesneler üzerinde sonsuz döngü olmaz:

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


Orijinal Yanıt

IE'yi bırakmayı ve esas olarak daha güncel tarayıcıları desteklemezseniz bunu yapmanın daha yeni bir yolu için ( kangax'ın es6 tablosunu uyumluluk için kontrol edin ). Bunun için es2015 jeneratörlerini kullanabilirsiniz . @ TheHippo'nun cevabını buna göre güncelledim. Tabii ki gerçekten IE desteği istiyorsanız babel JavaScript transpiler kullanabilirsiniz.

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

Yalnızca kendi numaralandırılabilir özelliklere (temel olarak prototip olmayan zincir özellikleri) sahip olmak istiyorsanız, bunu kullanarak yineleme Object.keysve for...ofdöngü yerine değiştirebilirsiniz:

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


Mükemmel cevap! Geçilen her anahtar için abc, abcd, vs. gibi yollar döndürmek mümkün müdür?
supersan

1
@supersan güncellenmiş kod snippet'lerimi görüntüleyebilirsiniz. Dizelerin bir dizisi olan her birine bir yol değişkeni ekledim. Dizideki dizeler, orijinal kaynak nesneden elde edilen yinelenen değere ulaşmak için erişilen her anahtarı temsil eder.
John

4

İşlem ve tetikleyici işlevleri kullanmadan @TheHippo'nun mükemmel çözümünü anonim bir işlevde kullanmak istedim. Aşağıdakiler benim gibi çalıştı, benim gibi acemi programcıları paylaştı.

(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

Çoğu Javascript motoru kuyruk özyinelemesini optimize etmez (JSON derinlemesine iç içe değilse bu bir sorun olmayabilir), ancak genellikle dikkatli olun ve bunun yerine yineleme yapın, örn.

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

Senaryom:

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 girişi:

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

İşlev Çağrısı:

callback_func(inp_json);

Benim ihtiyacına göre çıktı:

["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>



-1

Benim için en iyi çözüm şuydu:

basit ve herhangi bir çerçeve kullanmadan

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

-1

Bununla tüm anahtarları / değerleri alabilir ve hiyerarşiyi koruyabilirsiniz

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

Bu bir değişikliktir ( 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

Derin iç içe geçmiş JS nesnelerini dolaşmak ve düzenlemek için kütüphane oluşturdum. API'yı buradan kontrol edin: https://github.com/dominik791

Demo uygulamasını kullanarak kütüphaneyle etkileşimli olarak da oynayabilirsiniz: https://dominik791.github.io/obj-traverse-demo/

Kullanım örnekleri: Her zaman her yöntemin ilk parametresi olan kök nesneniz olmalıdır:

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

İkinci parametre her zaman iç içe nesneleri tutan özelliğin adıdır. Yukarıdaki durumda olurdu 'children'.

Üçüncü parametre bulmak / değiştirmek / silmek istediğiniz nesneyi / nesneleri bulmak için kullandığınız bir nesnedir. Örneğin, kimliği 1'e eşit olan bir nesne arıyorsanız { id: 1}, üçüncü parametre olarak geçersiniz .

Ve yapabilirsin:

  1. findFirst(rootObj, 'children', { id: 1 }) ile ilk nesneyi bulmak id === 1
  2. findAll(rootObj, 'children', { id: 1 }) ile tüm nesneleri bulmak için id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) eşleşen ilk nesneyi silmek için
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) eşleşen tüm nesneleri silmek için

replacementObj son iki yöntemde son parametre olarak kullanılır:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})değişime ilk önce nesneyi bulunan id === 1için{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})ile tüm nesneleri değiştirmek id === 1için{ id: 2, name: 'newObj'}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.