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

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

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

停止讨厌的无限对象遍历

这个经过编辑的答案仍然提供了我原始答案的附加好处之一,该答案允许您使用提供的生成器函数来使用更简洁简单的可迭代接口(请考虑使用for of循环,如for(var a of b)所示,其中b是可迭代的元素,而a是可迭代的元素)。通过使用generator函数以及一个更简单的api,它还可以帮助代码重用,因此您不必在要深入迭代对象属性的任何地方重复迭代逻辑,还可以实现如果您想更早地停止迭代,请退出循环。

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

同样在编辑示例中的生成器对象中,我选择使用Object.keys而不是for in,后者仅迭代对象上的非原型键。如果您希望包含原型密钥,则可以自己替换掉。有关使用Object.keysfor in的两种实现,请参见下面的原始答案部分。

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

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

如果只希望拥有自己的可枚举属性(基本上是非原型链属性),则可以使用Object.keysfor...of循环将其更改为迭代:

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: [ ... ]
    }
  ]
};

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

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

您可以:

  • findFirst(rootObj, 'children', { id: 1 })查找第一个对象
    id === 1
  • findAll(rootObj, 'children', { id: 1 })查找所有对象
    id === 1
  • findAndDeleteFirst(rootObj, 'children', { id: 1 })删除第一个匹配对象
  • findAndDeleteAll(rootObj, 'children', { id: 1 })删除所有匹配的对象
  • replacementObj用作最后两个方法中的最后一个参数:

  • findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})将使用id === 1的第一个找到的对象更改为{ id: 2, name: 'newObj'}
  • findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})将具有id === 1的所有对象更改为{ id: 2, name: 'newObj'}

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