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

我想遍历一个 JSON 对象树,但是找不到这方面的任何库。这看起来不难,但感觉像是重造轮子。

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

282508 次浏览

JSON 对象只是一个 Javascript 对象。这实际上就是 JSON 的意思: JSON。因此,您可以遍历一个 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 方法,因为它们使编写这样的代码更加容易。

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

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

有一个新的库可以用 JavaScript 来遍历 JSON 数据,它支持许多不同的用例。

Https://npmjs.org/package/traverse

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

它可以处理所有类型的 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]);
}
}
}

对我来说,最好的解决办法如下:

简单,没有使用任何框架

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

您可以获取所有键/值,并使用以下命令保留层次结构

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

我想在一个匿名函数中使用@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);

如果您正在遍历一个实际的 JSON 绳子,那么您可以使用 revver 函数。

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

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

我已经创建了一个库来遍历和编辑深度嵌套的 JS 对象

你也可以使用演示应用程序与图书馆进行交互: Https://dominik791.github.io/obj-traverse-demo/

使用例子: 应该始终有 root 对象,它是每个方法的第一个参数:

var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
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. 使用 id === 1将首次找到的对象更改为 { id: 2, name: 'newObj'}
  2. id === 1中的所有对象更改为 { id: 2, name: 'newObj'}

我的剧本:

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

简化答案原件

如果你不介意删除 IE 并且主要支持更多的当前浏览器的话,可以用一种更新的方式来做到这一点(请检查 Kangax 的第六桌的兼容性)。您可以使用 es2015发电机进行此操作。我相应地更新了“河马”的回答。当然,如果你真的想要 IE 的支持,你可以使用 BabelJavaScript 转发器。

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

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

阻止讨厌的无限物体穿越

这个经过编辑的答案仍然提供了我原始答案的一个额外好处,它允许你使用提供的 发电机功能发电机功能来使用一个更简洁的 可迭代的接口(想想在 for(var a of b)中使用 for of循环,其中 b是迭代的,而 a是迭代的一个元素)。通过使用生成器函数和一个更简单的 api,它还有助于代码重用,因为它可以让你不必在任何地方重复迭代逻辑,这样你就可以在对象的属性上进行深度迭代。如果你想提前停止迭代,它还可以让 break跳出循环。

我注意到有一点没有被提及,也没有出现在我的原始答案中,那就是您应该小心地遍历任意(即任何“随机”的)对象集,因为 JavaScript 对象可以自我引用。这就创造了进行无限循环遍历的机会。然而,未修改的 JSON 数据不能自我引用,所以如果您正在使用这个特定的 JS 对象子集,您不必担心无限循环遍历,您可以引用我的原始答案或其他答案。下面是一个无结束遍历的示例(请注意,它不是一段可运行的代码,否则会导致浏览器选项卡崩溃)。

在我编辑的例子中,我还选择了在生成器对象中使用 Object.keys而不是 for in,因为 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);
}

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

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

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 的请求从迭代器中产生的。Path 变量是一个字符串数组,其中数组中的每个字符串表示为从原始源对象获取结果迭代值而访问的每个键。路径变量可以输入到 Loash 的 get 函数/方法。或者您可以编写自己版本的 loash 的 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"]));

你也可以这样设置一个 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));

编辑2020年9月 : 为了更快地访问前一个对象,我添加了一个父对象。这可以让您更快地构建一个反向遍历器。你也可以修改遍历算法来做广度优先搜索,而不是深度优先,深度优先实际上可能更容易预测,这里是 一个带广度优先搜索的字体脚本版本。由于这是一个 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);
}

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>

我们使用 物体扫描完成许多数据处理任务。一旦你接受了它,它就会变得很强大。下面是如何进行基本遍历的方法

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

免责声明 : 我是 < a href = “ https://www.npmjs.com/package/object-scanner”rel = “ nofollow norefrer”> object-scanner 的作者

这将把所有节点读入地图。

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