简体   繁体   English

使用 JavaScript 遍历 JSON 对象树的所有节点

[英]Traverse all the Nodes of a JSON Object Tree with JavaScript

I'd like to traverse a JSON object tree, but cannot find any library for that.我想遍历 JSON 对象树,但找不到任何库。 It doesn't seem difficult but it feels like reinventing the wheel.这似乎并不困难,但感觉就像重新发明轮子。

In XML there are so many tutorials showing how to traverse an XML tree with DOM :(在 XML 中,有很多教程展示了如何使用 DOM 遍历 XML 树 :(

If you think jQuery is kind of overkill for such a primitive task, you could do something like that:如果你认为 jQuery 对于这样一个原始任务来说有点矫枉过正,你可以做这样的事情:

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

A JSON object is simply a Javascript object. JSON 对象只是一个 Javascript 对象。 That's actually what JSON stands for: JavaScript Object Notation.这实际上就是 JSON 的含义:JavaScript Object Notation。 So you'd traverse a JSON object however you'd choose to "traverse" a Javascript object in general.所以你会遍历一个 JSON 对象,但是你通常会选择“遍历”一个 Javascript 对象。

In ES2017 you would do:在 ES2017 中,你会这样做:

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

You can always write a function to recursively descend into the object:您始终可以编写一个函数来递归下降到对象中:

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

This should be a good starting point.这应该是一个很好的起点。 I highly recommend using modern javascript methods for such things, since they make writing such code much easier.我强烈建议使用现代 javascript 方法来处理这些事情,因为它们使编写此类代码变得更加容易。

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

There's a new library for traversing JSON data with JavaScript that supports many different use cases.有一个用于使用 JavaScript 遍历 JSON 数据的新库,该库支持许多不同的用例。

https://npmjs.org/package/traverse https://npmjs.org/package/traverse

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

It works with all kinds of JavaScript objects.它适用于各种 JavaScript 对象。 It even detects cycles.它甚至可以检测循环。

It provides the path of each node, too.它也提供了每个节点的路径。

Original Simplified Answer原始简化答案

For a newer way to do it if you don't mind dropping IE and mainly supporting more current browsers (check kangax's es6 table for compatibility).如果您不介意放弃 IE 并且主要支持更多当前的浏览器(检查kangax 的 es6 表以了解兼容性),则可以使用更新的方法来执行此操作。 You can use es2015 generators for this.您可以为此使用 es2015 生成器 I've updated @TheHippo's answer accordingly.我已经相应地更新了@TheHippo 的答案。 Of course if you really want IE support you can use the babel JavaScript transpiler.当然,如果你真的想要 IE 支持,你可以使用babel JavaScript 转译器。

 // Implementation of Traverse function* traverse(o, path=[]) { for (var i in o) { const itemPath = path.concat(i); yield [i,o[i],itemPath,o]; if (o[i] !== null && typeof(o[i])=="object") { //going one step down in the object tree!! yield* traverse(o[i], itemPath); } } } // Traverse usage: //that's all... no magic, no bloated framework for(var [key, value, path, parent] of traverse({ foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } })) { // do something here with each key and value console.log(key, value, path, parent); }

If you want only own enumerable properties (basically non-prototype chain properties) you can change it to iterate using Object.keys and a for...of loop instead:如果您只想要自己的可枚举属性(基本上是非原型链属性),您可以将其更改为使用Object.keysfor...of循环进行迭代:

 function* traverse(o,path=[]) { for (var i of Object.keys(o)) { const itemPath = path.concat(i); yield [i,o[i],itemPath,o]; 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, parent] of traverse({ foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } })) { // do something here with each key and value console.log(key, value, path, parent); }

EDIT : This edited answer solves infinite looping traversals.编辑:这个编辑过的答案解决了无限循环遍历。

Stopping Pesky Infinite Object Traversals停止讨厌的无限对象遍历

This edited answer still provides one of the added benefits of my original answer which allows you to use the provided generator function in order to use a cleaner and simple iterable interface (think using for of loops as in for(var a of b) where b is an iterable and a is an element of the iterable).这个编辑过的答案仍然提供了我原来的答案的附加好处之一,它允许您使用提供的生成器函数来使用更清晰和简单的可迭代界面(想想使用for of循环,如for(var a of b) where b是可迭代的,而a是可迭代的元素)。 By using the generator function along with being a simpler api it also helps with code reuse by making it so you don't have to repeat the iteration logic everywhere you want to iterate deeply on an object's properties and it also makes it possible to break out of the loop if you would like to stop iteration earlier.通过使用生成器函数以及更简单的 api,它还有助于代码重用,这样您就不必在任何想要深入迭代对象属性的地方重复迭代逻辑,并且还可以break如果您想更早地停止迭代,则循环的。

One thing that I notice that has not been addressed and that isn't in my original answer is that you should be careful traversing arbitrary (ie any "random" set of) objects, because JavaScript objects can be self referencing.我注意到的一件事尚未解决并且不在我的原始答案中是您应该小心遍历任意(即任何“随机”组)对象,因为 JavaScript 对象可以自引用。 This creates the opportunity to have infinite looping traversals.这创造了无限循环遍历的机会。 Unmodified JSON data however cannot be self referencing, so if you are using this particular subset of JS objects you don't have to worry about infinite looping traversals and you can refer to my original answer or other answers.然而,未修改的 JSON 数据不能自引用,因此如果您使用的是 JS 对象的这个特定子集,您不必担心无限循环遍历,您可以参考我的原始答案或其他答案。 Here is an example of a non-ending traversal (note it is not a runnable piece of code, because otherwise it would crash your browser tab).这是一个非结束遍历的例子(注意它不是一段可运行的代码,否则它会使你的浏览器选项卡崩溃)。

Also in the generator object in my edited example I opted to use Object.keys instead of for in which iterates only non-prototype keys on the object.同样在我编辑的示例中的生成器对象中,我选择使用Object.keys而不是for in其中仅迭代对象上的非原型键。 You can swap this out yourself if you want the prototype keys included.如果您想要包含原型密钥,您可以自己更换它。 See my original answer section below for both implementations with Object.keys and for in .有关Object.keysfor in两种实现,请参阅下面的原始答案部分。

Worse - This will infinite loop on self-referential objects:更糟糕的是 - 这将无限循环自引用对象:

function* traverse(o, path=[]) {
    for (var i of Object.keys(o)) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath, o]; 
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[i], itemPath);
        }
    }
}

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

// this self-referential property assignment is the only real logical difference 
// from the above original example which ends up making this naive traversal 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path, parent);
}

To save yourself from this you can add a set within a closure, so that when the function is first called it starts to build a memory of the objects it has seen and does not continue iteration once it comes across an already seen object.为了避免这种情况,你可以在闭包中添加一个集合,这样当函数第一次被调用时,它开始构建它已经看到的对象的内存,并且一旦遇到一个已经看到的对象就不会继续迭代。 The below code snippet does that and thus handles infinite looping cases.下面的代码片段就是这样做的,因此可以处理无限循环的情况。

Better - This will not infinite loop on self-referential objects:更好 - 这不会在自引用对象上无限循环:

 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, o]; if (o[i] !== null && typeof(o[i])=="object") { //going one step down in the object tree!! yield* innerTraversal(o[i], itemPath); } } } yield* innerTraversal(o); } //your object var o = { foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } }; /// this self-referential property assignment is the only real logical difference // from the above original example which makes more naive traversals // non-terminating (ie it makes it infinite loop) oo = o; console.log(o); //that's all... no magic, no bloated framework for(var [key, value, path, parent] of traverse(o)) { // do something here with each key and value console.log(key, value, path, parent); }


EDIT : All above examples in this answer have been edited to include a new path variable yielded from the iterator as per @supersan's request .编辑:此答案中的所有上述示例均已编辑,以包含根据@supersan 的请求从迭代器产生的新路径变量。 The path variable is an array of strings where each string in the array represents each key that was accessed to get to the resulting iterated value from the original source object. path 变量是一个字符串数组,其中数组中的每个字符串表示为从原始源对象获取结果迭代值而访问的每个键。 The path variable can be fed into lodash's get function/method .路径变量可以输入lodash 的 get function/method Or you could write your own version of lodash's get which handles only arrays like so:或者您可以编写自己的 lodash 版本,它只处理这样的数组:

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

You could also make a set function like so:你也可以像这样创建一个 set 函数:

 function set (object, path, value) { const obj = path.slice(0,-1).reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object) if(obj && obj[path[path.length - 1]]) { obj[path[path.length - 1]] = value; } return object; } const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }}; // these paths exist on the object console.log(set(example, ["a", "0"], 2)); console.log(set(example, ["c", "d", "0"], "qux")); console.log(set(example, ["b"], 12)); // these paths do not exist on the object console.log(set(example, ["e", "f", "g"], false)); console.log(set(example, ["b", "f", "g"], null));

EDIT Sep. 2020 : I added a parent for quicker access of the previous object.编辑 2020 年 9 月:我添加了一个父对象,以便更快地访问上一个对象。 This could allow you to more quickly build a reverse traverser.这可以让您更快地构建反向遍历器。 Also you could always modify the traversal algorithm to do breadth first search instead of depth first which is actually probably more predictable in fact here's a TypeScript version with Breadth First Search .此外,您始终可以修改遍历算法以进行广度优先搜索而不是深度优先,这实际上可能更可预测,事实上这是带有广度优先搜索的 TypeScript 版本 Since this is a JavaScript question I'll put the JS version here:由于这是一个 JavaScript 问题,我将把 JS 版本放在这里:

 var TraverseFilter; (function (TraverseFilter) { /** prevents the children from being iterated. */ TraverseFilter["reject"] = "reject"; })(TraverseFilter || (TraverseFilter = {})); function* traverse(o) { const memory = new Set(); function* innerTraversal(root) { const queue = []; queue.push([root, []]); while (queue.length > 0) { const [o, path] = queue.shift(); if (memory.has(o)) { // we've seen this object before don't iterate it continue; } // add the new object to our memory. memory.add(o); for (var i of Object.keys(o)) { const item = o[i]; const itemPath = path.concat([i]); const filter = yield [i, item, itemPath, o]; if (filter === TraverseFilter.reject) continue; if (item !== null && typeof item === "object") { //going one step down in the object tree!! queue.push([item, itemPath]); } } } } yield* innerTraversal(o); } //your object var o = { foo: "bar", arr: [1, 2, 3], subo: { foo2: "bar2" } }; /// this self-referential property assignment is the only real logical difference // from the above original example which makes more naive traversals // non-terminating (ie it makes it infinite loop) oo = o; //that's all... no magic, no bloated framework for (const [key, value, path, parent] of traverse(o)) { // do something here with each key and value console.log(key, value, path, parent); }

Depends on what you want to do.取决于你想做什么。 Here's an example of traversing a JavaScript object tree, printing keys and values as it goes:下面是一个遍历 JavaScript 对象树、打印键和值的示例:

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

If you're traversing an actual JSON string then you can use a reviver function.如果您要遍历实际的 JSON字符串,则可以使用 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)
})

When traversing an object:遍历对象时:

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

I wanted to use the perfect solution of @TheHippo in an anonymous function, without use of process and trigger functions.我想在匿名函数中使用@TheHippo 的完美解决方案,而不使用进程和触发器函数。 The following worked for me, sharing for novice programmers like myself.以下对我有用,分享给像我这样的新手程序员。

(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);

Most Javascript engines do not optimize tail recursion (this might not be an issue if your JSON isn't deeply nested), but I usually err on the side of caution and do iteration instead, eg大多数 Javascript 引擎不会优化尾递归(如果您的 JSON 没有深度嵌套,这可能不是问题),但我通常会谨慎行事并进行迭代,例如

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)

I've created library to traverse and edit deep nested JS objects.我创建了库来遍历和编辑深层嵌套的 JS 对象。 Check out API here: https://github.com/dominik791在此处查看 API: https : //github.com/dominik791

You can also play with the library interactively using demo app: https://dominik791.github.io/obj-traverse-demo/您还可以使用演示应用程序以交互方式使用库: https : //dominik791.github.io/obj-traverse-demo/

Examples of usage: You should always have root object which is the first parameter of each method:用法示例:您应该始终拥有根对象,它是每个方法的第一个参数:

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

The second parameter is always the name of property that holds nested objects.第二个参数始终是包含嵌套对象的属性的名称。 In above case it would be 'children' .在上述情况下,它将是'children'

The third parameter is an object that you use to find object/objects that you want to find/modify/delete.第三个参数是一个对象,用于查找要查找/修改/删除的对象。 For example if you're looking for object with id equal to 1, then you will pass { id: 1} as the third parameter.例如,如果您要查找 id 等于 1 的对象,那么您将传递{ id: 1}作为第三个参数。

And you can:你可以:

  1. findFirst(rootObj, 'children', { id: 1 }) to find first object with id === 1 findFirst(rootObj, 'children', { id: 1 })找到第一个id === 1对象
  2. findAll(rootObj, 'children', { id: 1 }) to find all objects with id === 1 findAll(rootObj, 'children', { id: 1 })查找所有id === 1对象
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) to delete first matching object findAndDeleteFirst(rootObj, 'children', { id: 1 })删除第一个匹配对象
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) to delete all matching objects findAndDeleteAll(rootObj, 'children', { id: 1 })删除所有匹配的对象

replacementObj is used as the last parameter in two last methods: replacementObj用作最后两个方法中的最后一个参数:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'}) to change first found object with id === 1 to the { id: 2, name: 'newObj'} findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})将第一个找到的id === 1对象更改为{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'}) to change all objects with id === 1 to the { id: 2, name: 'newObj'} findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})id === 1所有对象更改为{ id: 2, name: 'newObj'}

My Script:我的脚本:

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

Input JSON:输入 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": []
                    }
                ]
            }
       ]
   }
]

Function Call:函数调用:

callback_func(inp_json);

Output as per my Need:根据我的需要输出:

["output/f1/ver"]

 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>

We use object-scan for many data processing tasks.我们将对象扫描用于许多数据处理任务。 It's powerful once you wrap your head around it.一旦你把头环绕在它周围,它就很强大。 Here is how you could do basic traversal这是您如何进行基本遍历

 // const objectScan = require('object-scan'); const obj = { foo: 'bar', arr: [1, 2, 3], subo: { foo2: 'bar2' } }; objectScan(['**'], { reverse: false, filterFn: ({ key, value }) => { console.log(key, value); } })(obj); // => [ 'foo' ] bar // => [ 'arr', 0 ] 1 // => [ 'arr', 1 ] 2 // => [ 'arr', 2 ] 3 // => [ 'arr' ] [ 1, 2, 3 ] // => [ 'subo', 'foo2' ] bar2 // => [ 'subo' ] { foo2: 'bar2' }
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer : I'm the author of object-scan免责声明:我是对象扫描的作者

This Will read All Nodes to a map.这会将所有节点读取到地图。

 function readJsonFile() { let jsonString = getValueById("testDataContent"); let jsonObj = JSON.parse(jsonString); let jsonElements = []; jsonElements = traverse(jsonObj, jsonElements); console.log(jsonElements) } function traverse(jsonObj, jsonElements) { if (jsonObj !== null && typeof jsonObj == "object") { Object.entries(jsonObj).forEach(([key, value]) => { if (typeof value == "object") { var obj = []; let map = new Map(); map.set(key, traverse(value, obj)) jsonElements.push(map); } else { var obj = []; obj.key = key; obj.value = value; jsonElements.push(obj); } }); } else { } return jsonElements; }

The best solution for me was the following:对我来说最好的解决方案如下:

simple and without using any framework简单,不使用任何框架

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

You can get all keys / values and preserve the hierarchy with this您可以使用此获取所有键/值并保留层次结构

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

This is a modification on ( https://stackoverflow.com/a/25063574/1484447 )这是对( https://stackoverflow.com/a/25063574/1484447 )的修改

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM