Traverse all the Nodes of a JSON Object Tree with JavaScript
我想遍历JSON对象树,但是找不到任何库。 看起来似乎并不困难,但感觉就像是在重新发明轮子。
在XML中,有许多教程显示了如何使用DOM遍历XML树:(
如果您认为jQuery对于这种原始任务来说有点过头了,则可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //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); |
JSON对象只是Javascript对象。实际上,这就是JSON所代表的含义:JavaScript对象表示法。因此,您将遍历一个JSON对象,但是通常选择"遍历"一个Javascript对象。
在ES2017中,您将执行以下操作:
1 2 3 | Object.entries(jsonObj).forEach(([key, value]) => { // do something with key and val }); |
您始终可以编写一个函数来递归地归入对象:
1 2 3 4 5 6 7 8 9 10 11 | 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 } } |
这应该是一个很好的起点。我强烈建议对此类事情使用现代javascript方法,因为它们使编写此类代码变得更加容易。
1 2 3 4 5 6 7 8 9 10 | 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]); } } } |
有一个新的库用于使用JavaScript遍历JSON数据,该库支持许多不同的用例。
https://npmjs.org/package/traverse
https://github.com/substack/js-traverse
它适用于各种JavaScript对象。它甚至可以检测周期。
它也提供每个节点的路径。
取决于您要做什么。这是遍历JavaScript对象树,在运行时打印键和值的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
如果您要遍历实际的JSON字符串,则可以使用reviver函数。
1 2 3 4 5 6 7 8 9 10 11 12 | 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) }) |
遍历对象时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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) }) |
编辑:此答案中所有下面的示例均已被编辑,以包括根据@supersan的请求从迭代器产生的新路径变量。 path变量是一个字符串数组,其中数组中的每个字符串代表每个键,该键被访问以从原始源对象获得最终的迭代值。可以将路径变量输入lodash的get函数/方法中。或者,您可以编写自己的lodash的get版本,该版本仅处理如下数组:
1 2 3 4 5 6 7 8 9 10 11 12 | 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"])); |
编辑:此编辑的答案解决了无限循环遍历。
停止讨厌的无限对象遍历
这个经过编辑的答案仍然提供了我原始答案的附加好处之一,该答案允许您使用提供的生成器函数来使用更简洁简单的可迭代接口(请考虑使用
我注意到尚未解决且我的原始答案中没有的一件事是,您应该小心地遍历任意(即,任何"随机"对象)对象,因为JavaScript对象可以是自引用的。这创造了无限循环遍历的机会。但是,未修改的JSON数据不能自引用,因此,如果您使用JS对象的此特定子集,则不必担心无限循环遍历,您可以参考我的原始答案或其他答案。这是一个无休止遍历的示例(请注意,它不是一段可运行的代码,因为否则它将导致浏览器选项卡崩溃)。
同样在编辑示例中的生成器对象中,我选择使用
更糟-这将在自引用对象上无限循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //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); } |
为了避免这种情况,您可以在闭包内添加一个集合,这样,在首次调用该函数时,它将开始为已看到的对象建立内存,并且一旦遇到已看到的对象就不会继续迭代。下面的代码段可以做到这一点,从而处理无限循环的情况。
更好-这不会对自引用对象造成无限循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | //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); } |
原始答案
如果您不介意丢弃IE并且主要支持更多当前浏览器,那么这是更新的方法(请检查kangax的es6表是否具有兼容性)。您可以为此使用es2015生成器。我已经相应更新了@TheHippo的答案。当然,如果您确实需要IE支持,则可以使用babel JavaScript Transpiler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //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); } |
如果只希望拥有自己的可枚举属性(基本上是非原型链属性),则可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //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); } |
我想在匿名函数中使用@TheHippo的完美解决方案,而不使用进程和触发器函数。以下内容对我有用,并为像我这样的新手程序员提供了共享。
1 2 3 4 5 6 7 8 9 10 11 | (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); |
大多数Javascript引擎不会优化尾部递归(如果您的JSON没有深度嵌套,这可能不会成为问题),但是我通常会谨慎行事,而是进行迭代,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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); |
1 | <textarea style="width:100%;height:600px;" id="textarea"></textarea> |
我的剧本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | [ { "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": [] } ] } ] } ] |
函数调用:
1 | callback_func(inp_json); |
根据我的需要输出:
1 | ["output/f1/ver"] |
我创建了一个库来遍历和编辑深层嵌套的JS对象。在此处查看API:https://github.com/dominik791
您还可以使用演示应用程序与图书馆互动玩耍:
https://dominik791.github.io/obj-traverse-demo/
用法示例:
您应该始终具有根对象,它是每个方法的第一个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var rootObj = { name: 'rootObject', children: [ { 'name': 'child1', children: [ ... ] }, { 'name': 'child2', children: [ ... ] } ] }; |
第二个参数始终是包含嵌套对象的属性的名称。在上述情况下,该值为
第三个参数是用于查找要查找/修改/删除的一个或多个对象的对象。例如,如果要查找id等于1的对象,则将传递
您可以:
与
与
1 2 3 4 5 6 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 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') |
这是对(https://stackoverflow.com/a/25063574/1484447)的修改
对我来说最好的解决方案是:
简单且无需使用任何框架
1 2 3 4 5 6 7 8 | var doSomethingForAll = function (arg) { if (arg != undefined && arg.length > 0) { arg.map(function (item) { // do something for item doSomethingForAll (item.subitem) }); } } |