Lintasi semua Node dari Pohon Objek JSON dengan JavaScript

148

Saya ingin melintasi pohon objek JSON, tetapi tidak dapat menemukan perpustakaan untuk itu. Tampaknya tidak sulit tetapi rasanya seperti menciptakan kembali roda.

Dalam XML ada begitu banyak tutorial yang menunjukkan cara melintasi pohon XML dengan DOM :(

Patsy Issa
sumber
1
Membuat iterator IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/... ia telah ditentukan (dasar) DepthFirst & BreadthFirst berikutnya dan kemampuan untuk bergerak di dalam struktur JSON tanpa rekursi.
Tom

Jawaban:

222

Jika Anda berpikir jQuery agak berlebihan untuk tugas primitif seperti itu, Anda bisa melakukan sesuatu seperti itu:

//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
sumber
2
Mengapa fund.apply (ini, ...)? Bukankah seharusnya func.apply (o, ...)?
Craig Celeste
4
@ParchedSquid No. Jika Anda melihat dokumen API untuk apply () parameter pertama adalah thisnilai dalam fungsi target, sedangkan oharus menjadi parameter pertama ke fungsi. Mengaturnya ke this(yang akan menjadi traversefungsi) agak aneh, tapi itu tidak seperti processmenggunakan thisreferensi. Itu bisa saja nol.
Thor84no
1
Untuk jshint dalam mode ketat meskipun Anda mungkin perlu menambahkan di /*jshint validthis: true */atas func.apply(this,[i,o[i]]);untuk menghindari kesalahan yang W040: Possible strict violation.disebabkan oleh penggunaanthis
Jasdeep Khalsa
4
@jasdeepkhalsa: Itu benar. Tetapi pada saat penulisan jawaban, jshint bahkan tidak dimulai sebagai proyek selama satu setengah tahun.
TheHippo
1
@Vishal Anda bisa menambahkan 3 parameter ke traversefungsi yang melacak kedalaman. Saat memanggil secara rekursif, tambahkan 1 ke level saat ini.
TheHippo
75

Objek JSON hanyalah objek Javascript. Itulah sebenarnya singkatan JSON: JavaScript Object Notation. Jadi Anda akan melintasi objek JSON namun Anda akan memilih untuk "melintasi" objek Javascript secara umum.

Dalam ES2017 Anda akan melakukan:

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

Anda selalu dapat menulis fungsi untuk turun secara rekursif ke objek:

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

Ini harus menjadi titik awal yang baik. Saya sangat merekomendasikan menggunakan metode javascript modern untuk hal-hal seperti itu, karena mereka membuat penulisan kode seperti itu jauh lebih mudah.

Eli Courtwright
sumber
9
Hindari lintasan (v) di mana v == null, karena (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim
4
Aku benci kedengarannya terlalu berlebihan, tapi kupikir sudah ada banyak kebingungan tentang ini, jadi hanya demi kejelasan aku mengatakan yang berikut. Objek JSON dan JavaScript bukan hal yang sama. JSON didasarkan pada pemformatan objek JavaScript, tetapi JSON hanyalah notasi ; itu adalah serangkaian karakter yang mewakili suatu objek. Semua JSON dapat "diuraikan" menjadi objek JS, tetapi tidak semua objek JS dapat "dirubah" menjadi JSON. Misalnya objek JS referensial diri tidak dapat dirender.
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
sumber
6
Bisakah Anda menjelaskan mengapa demikian much better?
Demensik
3
Jika metode ini dimaksudkan untuk melakukan apa pun selain log Anda harus memeriksa null, null masih merupakan objek.
wi1
3
@ wi1 Setuju dengan Anda, bisa memeriksa!!o[i] && typeof o[i] == 'object'
pilau
32

Ada perpustakaan baru untuk melintasi data JSON dengan JavaScript yang mendukung banyak kasus penggunaan yang berbeda.

https://npmjs.org/package/traverse

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

Ini bekerja dengan semua jenis objek JavaScript. Bahkan mendeteksi siklus.

Ini menyediakan jalur dari setiap node juga.

Benjamin Atkin
sumber
1
js-traverse tampaknya juga tersedia melalui npm di node.js.
Ville
Iya. Hanya disebut melintasi di sana. Dan mereka memiliki halaman web yang indah! Memperbarui jawaban saya untuk memasukkannya.
Benjamin Atkin
15

Tergantung pada apa yang ingin Anda lakukan. Berikut adalah contoh melintasi pohon objek JavaScript, mencetak kunci dan nilai saat berjalan:

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
sumber
9

Jika Anda melintasi string JSON yang sebenarnya, maka Anda dapat menggunakan fungsi 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)
})

Saat melintasi objek:

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
sumber
8

EDIT : Semua contoh di bawah dalam jawaban ini telah diedit untuk menyertakan variabel jalur baru yang dihasilkan dari iterator sesuai permintaan @ supersan . Variabel path adalah array string di mana setiap string dalam array mewakili setiap kunci yang diakses untuk mendapatkan nilai iterasi yang dihasilkan dari objek sumber asli. Variabel path dapat dimasukkan ke dalam get function / method lodash . Atau Anda dapat menulis versi get lodash Anda sendiri yang hanya menangani array seperti:

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 : Jawaban yang diedit ini memecahkan traversal infinite looping.

Menghentikan Pesky Infinite Object Traversals

Jawaban yang diedit ini masih memberikan salah satu manfaat tambahan dari jawaban asli saya yang memungkinkan Anda untuk menggunakan fungsi generator yang disediakan untuk menggunakan antarmuka yang lebih bersih dan sederhana yang dapat diubah (pikirkan menggunakan for ofloop seperti di for(var a of b)mana biterable dan amerupakan elemen iterable ). Dengan menggunakan fungsi generator dan menjadi api yang lebih sederhana, ia juga membantu penggunaan kembali kode dengan membuatnya jadi Anda tidak perlu mengulangi logika iterasi di mana pun Anda ingin mengulangi secara mendalam pada properti objek dan juga memungkinkan untuk breakkeluar dari loop jika Anda ingin menghentikan iterasi lebih awal.

Satu hal yang saya perhatikan yang belum diatasi dan yang tidak ada dalam jawaban asli saya adalah bahwa Anda harus berhati-hati melintasi objek yang sewenang-wenang (yaitu set "acak"), karena objek JavaScript dapat merujuk sendiri. Ini menciptakan peluang untuk memiliki traverse looping tanpa batas. Namun data JSON yang tidak dimodifikasi tidak dapat menjadi referensi sendiri, jadi jika Anda menggunakan subset objek JS ini, Anda tidak perlu khawatir tentang perulangan perulangan tak terbatas dan Anda dapat merujuk ke jawaban asli saya atau jawaban lain. Berikut ini adalah contoh dari traversal tanpa akhir (perhatikan itu bukan sepotong kode yang bisa dijalankan, karena jika tidak maka akan crash tab browser Anda).

Juga dalam objek generator dalam contoh saya yang diedit saya memilih untuk menggunakan Object.keysalih-alih for inhanya kunci non-prototipe pada objek. Anda dapat menukar ini sendiri jika Anda ingin kunci prototipe disertakan. Lihat bagian jawaban asli saya di bawah ini untuk implementasi dengan Object.keysdan for in.

Lebih buruk - Ini akan infinite loop pada objek referensial diri:

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

Untuk menyelamatkan diri Anda dari ini, Anda dapat menambahkan satu set dalam penutupan, sehingga ketika fungsi pertama kali dipanggil itu mulai membangun memori objek yang telah dilihat dan tidak melanjutkan iterasi setelah menemukan objek yang sudah terlihat. Cuplikan kode di bawah melakukan hal itu dan dengan demikian menangani kasus pengulangan yang tak terbatas.

Lebih baik - Ini tidak akan infinite loop pada objek referensial diri:

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


Jawaban Asli

Untuk cara yang lebih baru untuk melakukannya jika Anda tidak keberatan menjatuhkan IE dan terutama mendukung lebih banyak browser saat ini (periksa tabel ES6 kangax untuk kompatibilitas). Anda dapat menggunakan generator es2015 untuk ini. Saya telah memperbarui jawaban TheHippo. Tentu saja jika Anda benar-benar menginginkan dukungan IE Anda dapat menggunakan babel JavaScript transpiler.

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

Jika Anda hanya ingin memiliki properti enumerable (pada dasarnya properti rantai non-prototipe), Anda dapat mengubahnya untuk beralih menggunakan Object.keysdan for...ofloop sebagai gantinya:

//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
sumber
Jawaban bagus! Apakah mungkin untuk mengembalikan jalur seperti abc, abcd, dll untuk setiap kunci yang sedang dilalui?
supersan
1
@supersan Anda dapat melihat cuplikan kode saya yang diperbarui. Saya menambahkan variabel path ke masing-masing yang merupakan array dari string. String dalam array mewakili setiap kunci yang diakses untuk mendapatkan nilai berulang yang dihasilkan dari objek sumber asli.
John
4

Saya ingin menggunakan solusi sempurna dari @TheHippo dalam fungsi anonim, tanpa menggunakan proses dan fungsi pemicu. Berikut ini bekerja untuk saya, berbagi untuk programmer pemula seperti saya.

(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
sumber
2

Sebagian besar mesin Javascript tidak mengoptimalkan rekursi ekor (ini mungkin bukan masalah jika JSON Anda tidak bersarang secara mendalam), tetapi saya biasanya melakukan kesalahan dengan berhati-hati dan melakukan iterasi sebagai gantinya, misalnya

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
sumber
0

Skrip saya:

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

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

Panggilan fungsi:

callback_func(inp_json);

Output sesuai Kebutuhan saya:

["output/f1/ver"]
Mohideen bin Mohammed
sumber
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
sumber
berhasil mengirimkan formulir enctype applicatioin / json
seung
-1

Solusi terbaik bagi saya adalah sebagai berikut:

sederhana dan tanpa menggunakan kerangka apa pun

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

Anda bisa mendapatkan semua kunci / nilai dan mempertahankan hierarki dengan ini

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

Ini adalah modifikasi pada ( https://stackoverflow.com/a/25063574/1484447 )

Ricky
sumber
-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
sumber
-1

Saya telah membuat pustaka untuk melintasi dan mengedit objek JS yang bersarang. Lihat API di sini: https://github.com/dominik791

Anda juga dapat bermain dengan perpustakaan secara interaktif menggunakan aplikasi demo: https://dominik791.github.io/obj-traverse-demo/

Contoh penggunaan: Anda harus selalu memiliki objek root yang merupakan parameter pertama dari setiap metode:

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

Parameter kedua selalu nama properti yang menyimpan objek bersarang. Dalam kasus di atas akan menjadi 'children'.

Parameter ketiga adalah objek yang Anda gunakan untuk menemukan objek / objek yang ingin Anda temukan / ubah / hapus. Misalnya jika Anda mencari objek dengan id sama dengan 1, maka Anda akan lulus { id: 1}sebagai parameter ketiga.

Dan kamu bisa:

  1. findFirst(rootObj, 'children', { id: 1 }) untuk menemukan objek pertama dengan id === 1
  2. findAll(rootObj, 'children', { id: 1 }) untuk menemukan semua objek dengan id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) untuk menghapus objek yang cocok pertama
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) untuk menghapus semua objek yang cocok

replacementObj digunakan sebagai parameter terakhir dalam dua metode terakhir:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})untuk mengubah objek yang ditemukan pertama dengan id === 1ke{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})untuk mengubah semua objek dengan id === 1ke{ id: 2, name: 'newObj'}
dominik791
sumber