我想遍历JSON对象树,但是找不到任何库。看起来并不困难,但感觉就像是在重新发明轮子。
在XML中,有许多教程显示了如何使用DOM遍历XML树:(
如果您认为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);
JSON对象只是一个Javascript对象。实际上,这就是JSON所代表的含义:JavaScript对象表示法。因此,您将遍历一个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方法,因为它们使编写此类代码变得更加容易。
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对象树,并随即打印键和值的示例:
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函数。
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)
})
原始简化答案
如果您不介意删除IE并且主要支持更多当前浏览器,那么这是一种更新的方法(请检查kangax的es6表是否具有兼容性)。您可以为此使用es2015生成器。我已经相应更新了@TheHippo的答案。当然,如果您确实需要IE支持,则可以使用babel JavaScript Transpiler。
// 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.keys
和for...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 of
循环,如for(var a of b)
whereb
是可迭代的并且a
是可迭代的元素)。通过使用发电机功能与使它成为一个更简单的API,它也与代码重用帮助一起,这样你就不必再重复无处不在,你想迭代深深的一个对象的属性迭代逻辑,同时也使得有可能break
出如果您想更早地停止迭代,则返回循环。
我注意到尚未解决且我的原始答案中没有的一件事是,您应该小心地遍历任意(即,任何“随机”集合)对象,因为JavaScript对象可以是自引用的。这创造了无限循环遍历的机会。但是,未修改的JSON数据不能自引用,因此,如果您使用JS对象的此特定子集,则不必担心无限循环遍历,您可以参考我的原始答案或其他答案。这是一个无休止遍历的示例(请注意,这不是一段可运行的代码,因为否则它将导致您的浏览器选项卡崩溃)。
同样在我的编辑示例中的生成器对象中,我选择使用,Object.keys
而不是for in
仅迭代对象上的非原型键。如果您希望包含原型密钥,则可以自己换掉。有关Object.keys
和的两种实现,请参见下面的原始答案部分for 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变量是一个字符串数组,其中数组中的每个字符串代表每个键,该键被访问以从原始源对象获得最终的迭代值。可以将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"]));
您还可以像下面这样设置函数:
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月:我添加了一个父级,以更快地访问上一个对象。这样可以使您更快地构建反向遍历器。另外,您始终可以修改遍历算法,以进行广度优先搜索而不是深度优先,实际上这可能更可预测,实际上这是带有广度优先搜索的TypeScript版本。由于这是一个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);
}
我想在匿名函数中使用@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);
大多数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对象。在此处查看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}
作为第三个参数传递。
您可以:
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'}
我的剧本:
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"]
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(['**'], {
filterFn: ({ key, value }) => {
console.log(key, value);
}
})(obj);
/* =>
[ 'subo', 'foo2' ] 'bar2'
[ 'subo' ] { foo2: 'bar2' }
[ 'arr', 2 ] 3
[ 'arr', 1 ] 2
[ 'arr', 0 ] 1
[ 'arr' ] [ 1, 2, 3 ]
[ 'foo' ] 'bar'
*/
对我来说最好的解决方案是:
简单且无需使用任何框架
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)的修改
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
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!