【问题标题】:Traverse all the Nodes of a JSON Object Tree with JavaScript使用 JavaScript 遍历 JSON 对象树的所有节点
【发布时间】:2010-10-17 21:13:35
【问题描述】:

我想遍历 JSON 对象树,但找不到任何库。看起来并不难,但感觉就像在重新发明轮子。

在 XML 中有很多教程展示了如何使用 DOM 遍历 XML 树 :(

【问题讨论】:

标签: javascript json


【解决方案1】:

JSON 对象只是一个 Javascript 对象。这实际上就是 JSON 的含义:JavaScript Object Notation。所以你会遍历一个 JSON 对象,但是你通常会选择“遍历”一个 Javascript 对象。

在 ES2017 中你会这样做:

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

您总是可以编写一个函数来递归地下降到对象中:

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 方法,因为它们使编写此类代码变得更加容易。

【讨论】:

  • 避免 traverse(v) where v==null,因为 (typeof null == "object") === true。 function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
  • 我讨厌听起来很迂腐,但我认为这已经有很多困惑了,所以为了清楚起见,我说以下内容。 JSON 和 JavaScript 对象不是一回事。 JSON 基于 JavaScript 对象的格式,但 JSON 只是 表示法;它是表示对象的字符串。所有 JSON 都可以“解析”成 JS 对象,但并非所有 JS 对象都可以“字符串化”成 JSON。例如自引用 JS 对象不能被字符串化。
【解决方案2】:

如果你认为 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);

【讨论】:

  • 为什么要fund.apply(this,...)?不应该是 func.apply(o,...) 吗?
  • @ParchedSquid 不。如果你查看API docs for apply(),第一个参数是目标函数中的this 值,而o 应该是函数的第一个参数。将其设置为this(这将是traverse 函数)虽然有点奇怪,但它不像process 那样使用this 引用。它也可以为空。
  • 对于严格模式下的jshint,虽然你可能需要在func.apply(this,[i,o[i]]);上方添加/*jshint validthis: true */以避免使用this导致的错误W040: Possible strict violation.
  • @jasdeepkhalsa:确实如此。但是在撰写答案时,jshint 甚至还没有作为一个项目启动一年半。
  • @Vishal 您可以将 3 参数添加到跟踪深度的 traverse 函数中。 Wenn 调用递归地将当前级别加 1。
【解决方案3】:

取决于你想做什么。这是一个遍历 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

【讨论】:

    【解决方案4】:

    有一个用于使用 JavaScript 遍历 JSON 数据的新库,支持许多不同的用例。

    https://npmjs.org/package/traverse

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

    它适用于各种 JavaScript 对象。它甚至可以检测周期。

    它也提供了每个节点的路径。

    【讨论】:

    • js-traverse 似乎也可以通过 node.js 中的 npm 获得。
    • 是的。它只是被称为 traverse 那里。他们有一个可爱的网页!更新我的答案以包含它。
    【解决方案5】:
    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]);
            }
        }
    }
    

    【讨论】:

    • 如果该方法的目的是做日志以外的任何事情,你应该检查 null,null 仍然是一个对象。
    • @wi1 同意,可以检查!!o[i] && typeof o[i] == 'object'
    【解决方案6】:

    对我来说最好的解决方案如下:

    简单且不使用任何框架

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

    【讨论】:

      【解决方案7】:

      您可以获取所有键/值并使用此保留层次结构

      // 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) 的修改

      【讨论】:

        【解决方案8】:

        我想在匿名函数中使用@TheHippo 的完美解决方案,而不使用进程和触发器函数。以下对我有用,为像我这样的新手程序员分享。

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

        【讨论】:

          【解决方案9】:

          如果您正在遍历一个实际的 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)
          })
          

          遍历对象时:

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

          【讨论】:

            【解决方案10】:
                         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>);
                             }
            

            【讨论】:

              【解决方案11】:

              大多数 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)
              

              【讨论】:

                【解决方案12】:

                我创建了一个库来遍历和编辑深层嵌套的 JS 对象。在此处查看 API:https://github.com/dominik791

                您还可以使用演示应用程序以交互方式使用库: https://dominik791.github.io/obj-traverse-demo/

                使用示例: 您应该始终拥有作为每个方法的第一个参数的根对象:

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

                第二个参数始终是包含嵌套对象的属性的名称。在上述情况下,它将是'children'

                第三个参数是一个对象,用于查找要查找/修改/删除的对象。例如,如果您要查找 id 等于 1 的对象,那么您将传递 { id: 1} 作为第三个参数。

                你可以:

                1. findFirst(rootObj, 'children', { id: 1 }) 找到第一个对象 与id === 1
                2. findAll(rootObj, 'children', { id: 1 }) 查找所有对象 与id === 1
                3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) 删除第一个匹配的对象
                4. findAndDeleteAll(rootObj, 'children', { id: 1 }) 删除所有匹配的对象

                replacementObj在最后两个方法中用作最后一个参数:

                1. 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'}) 将所有带有id === 1 的对象更改为{ id: 2, name: 'newObj'}

                【讨论】:

                  【解决方案13】:

                  我的脚本:

                  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:

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

                  函数调用:

                  callback_func(inp_json);
                  

                  根据我的需要输出:

                  ["output/f1/ver"]
                  

                  【讨论】:

                    【解决方案14】:

                    原始简化答案

                    如果您不介意放弃 IE 并主要支持更多当前浏览器,请使用更新的方法(请查看 kangax's es6 table 以了解兼容性)。您可以为此使用 es2015 generators。我已经相应地更新了@TheHippo 的答案。当然,如果你真的想要 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);
                    }

                    如果您只想要自己的可枚举属性(基本上是非原型链属性),您可以将其更改为使用 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);
                    }

                    编辑:这个编辑后的答案解决了无限循环遍历。

                    停止讨厌的无限对象遍历

                    这个编辑后的答案仍然提供了我原始答案的额外好处之一,它允许您使用提供的generator function 以使用更简洁的iterable interface(考虑使用for of 循环,如for(var a of b)其中b 是可迭代的,a 是可迭代的元素)。通过使用生成器函数以及作为一个更简单的 api,它还有助于代码重用,因此您不必在任何想要深入迭代对象属性的地方重复迭代逻辑,并且还可以 @987654339 @ 退出循环,如果您想提前停止迭代。

                    我注意到尚未解决且不在我的原始答案中的一件事是,您应该小心遍历任意(即任何“随机”集合)对象,因为 JavaScript 对象可以是自引用的。这创造了无限循环遍历的机会。然而,未修改的 JSON 数据不能自引用,因此如果您使用这个特定的 JS 对象子集,您不必担心无限循环遍历,您可以参考我的原始答案或其他答案。这是一个非结束遍历的示例(注意它不是一段可运行的代码,否则它会导致您的浏览器选项卡崩溃)。

                    此外,在我编辑的示例中的生成器对象中,我选择使用Object.keys 而不是for in,它只迭代对象上的非原型键。如果您想要包含原型键,您可以自己换掉它。请参阅下面我的原始答案部分,了解Object.keysfor in 的两种实现。

                    更糟 - 这将在自引用对象上无限循环:

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

                    为了避免这种情况,你可以在闭包中添加一个集合,这样当函数第一次被调用时,它就开始构建它已经看到的对象的内存,并且一旦遇到已经看到的对象就不会继续迭代.下面的代码 sn-p 就是这样做的,因此可以处理无限循环的情况。

                    更好 - 这不会在自引用对象上无限循环:

                    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 (i.e. it makes it infinite loop)
                    o.o = 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);
                    }

                    编辑:此答案中的所有上述示例都经过编辑,以包含根据@supersan's request 从迭代器产生的新路径变量。 path 变量是一个字符串数组,其中数组中的每个字符串代表每个键,这些键被访问以从原始源对象获取结果迭代值。路径变量可以输入lodash's get function/method。或者你可以编写你自己的 lodash 的 get 版本,它只处理这样的数组:

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

                    你也可以这样设置函数:

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

                    2020 年 9 月编辑:我添加了一个父对象,以便更快地访问上一个对象。这可以让您更快地构建反向遍历器。您也可以随时修改遍历算法以进行广度优先搜索而不是深度优先搜索,这实际上可能更可预测,事实上这里是a TypeScript version with Breadth First Search。由于这是一个 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 (i.e. it makes it infinite loop)
                    o.o = 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);
                    }

                    【讨论】:

                    • 很好的答案!是否可以为正在遍历的每个键返回 a.b.c、a.b.c.d 等路径?
                    • @supersan 你可以查看我更新的代码 sn-ps。我为每个字符串数组添加了一个路径变量。数组中的字符串表示为从原始源对象获取结果迭代值而访问的每个键。
                    【解决方案15】:

                    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);
                    &lt;textarea style="width:100%;height:600px;" id="textarea"&gt;&lt;/textarea&gt;

                    【讨论】:

                    • 提交表单 enctype applicatioin/json
                    【解决方案16】:

                    我们将object-scan 用于许多数据处理任务。一旦你把头绕在它周围,它就会很强大。以下是基本遍历的方法

                    // 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}
                    &lt;script src="https://bundle.run/object-scan@13.8.0"&gt;&lt;/script&gt;

                    免责声明:我是object-scan的作者

                    【讨论】:

                      猜你喜欢
                      • 2015-01-19
                      • 2012-04-26
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-02-16
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多