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

140 javascript json

我想遍历一个JSON对象树,但找不到任何库.这似乎并不困难,但感觉就像重新发明轮子一样.

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

The*_*ppo 212

如果你认为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);
Run Code Online (Sandbox Code Playgroud)

  • @ParchedSquid No.如果你看一下[API docs for apply()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply),第一个参数是目标函数中的`this`值,而`o`应该是函数的第一个参数.将它设置为`this`(这将是`traverse`函数)虽然有点奇怪,但它并不像`process`那样使用`this`引用.它也可能是空的. (4认同)
  • @jasdeepkhalsa:那是真的.但在撰写答案时,jshint甚至还没有作为一个半年的项目开始. (4认同)
  • 为什么fund.apply(这,......)?不应该是func.apply(o,...)? (2认同)
  • 对于严格模式下的 jshint,您可能需要在 `func.apply(this,[i,o[i]]);` 上方添加 `/*jshint validthis: true */` 以避免错误 `W040: 可能的严格违规.` 由使用 `this` 引起 (2认同)
  • for 循环中缺少“hasOwnProperty”检查。 (2认同)

Eli*_*ght 63

JSON对象只是一个Javascript对象.这实际上就是JSON所代表的:JavaScript Object Notation.因此,您将遍历JSON对象,但是您通常会选择"遍历"Javascript对象.

在ES2017中你会做:

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

您始终可以编写函数以递归方式下降到对象中:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

这应该是一个很好的起点.我强烈建议使用现代的javascript方法来处理这类事情,因为它们使得编写这样的代码变得更加容易.

  • 避免遍历(v)其中v == null,因为(typeof null =="object")=== true.`function traverse(jsonObj){if(jsonObj && typeof jsonObj =="object"){...` (7认同)
  • 我不想听起来迂腐,但我认为对此已经有很多困惑,所以为了清楚起见,我说以下。JSON 和 JavaScript 对象不是一回事。JSON 基于 JavaScript 对象的格式,但 JSON 只是 **notation**;它是代表一个对象的字符串。所有 JSON 都可以“解析”为 JS 对象,但并非所有 JS 对象都可以“字符串化”为 JSON。例如,不能将自引用 JS 对象字符串化。 (5认同)

小智 35

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]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你能解释一下它为什么"好多了"? (5认同)
  • 如果该方法除了log之外还要执行任何操作,则应检查null,null仍然是一个对象. (3认同)
  • @ wi1同意你,可以检查`!! o [i] && typeof o [i] =='object' (3认同)

Ben*_*kin 27

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

https://npmjs.org/package/traverse

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

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

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


Bri*_*ell 13

取决于你想做什么.这是一个遍历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
Run Code Online (Sandbox Code Playgroud)


Dav*_*ane 7

如果您正在遍历实际的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)
})
Run Code Online (Sandbox Code Playgroud)

遍历对象时:

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)
})
Run Code Online (Sandbox Code Playgroud)


Joh*_*ohn 5

编辑:此答案中所有下面的示例均已编辑,以包含根据@supersan的请求从迭代器产生的新路径变量。path变量是一个字符串数组,其中数组中的每个字符串代表每个键,该键被访问以从原始源对象获得最终的迭代值。可以将path变量输入lodash的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"]));
Run Code Online (Sandbox Code Playgroud)

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

停止讨厌的无限对象遍历

这个经过编辑的答案仍然提供了我原始答案的附加好处之一,它允许您使用提供的生成器函数来使用更简洁简单的可迭代接口(请考虑使用for of循环,如for(var a of b)where b是可迭代的并且a是可迭代的元素)。通过使用发电机功能与使它成为一个更简单的API,它也与代码重用帮助一起,这样你就不必再重复无处不在,你想迭代深深的一个对象的属性迭代逻辑,同时也使得有可能break出如果您想提前停止迭代,则返回循环。

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

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

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

//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);
}
Run Code Online (Sandbox Code Playgroud)

为了避免这种情况,您可以在闭包内添加一个集合,这样,在首次调用该函数时,它将开始为已看到的对象建立内存,并且一旦遇到已看到的对象就不会继续迭代。下面的代码段可以做到这一点,从而处理无限循环的情况。

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

//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);
}
Run Code Online (Sandbox Code Playgroud)


原始答案

如果您不介意丢弃IE并且主要支持更多最新浏览器,那么这是更新的方法(请检查kangax的es6表是否具有兼容性)。您可以为此使用es2015 生成器。我已经相应更新了@TheHippo的答案。当然,如果您确实需要IE支持,则可以使用babel JavaScript Transpiler。

//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);
}
Run Code Online (Sandbox Code Playgroud)

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

//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);
}
Run Code Online (Sandbox Code Playgroud)