遍历嵌套的 JavaScript 对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8085004/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Iterate through Nested JavaScript Objects
提问by NewToThis
I'm trying to iterate through a nested object to retrieve a specific object identified by a string. In the sample object below, the identifier string is the "label" property. I can't wrap my head around how to iterate down through the tree to return the appropriate object. Any help or suggestions would be greatly appreciated.
我正在尝试遍历嵌套对象以检索由字符串标识的特定对象。在下面的示例对象中,标识符字符串是“标签”属性。我无法思考如何遍历树以返回适当的对象。任何帮助或建议将不胜感激。
var cars = {
label: 'Autos',
subs: [
{
label: 'SUVs',
subs: []
},
{
label: 'Trucks',
subs: [
{
label: '2 Wheel Drive',
subs: []
},
{
label: '4 Wheel Drive',
subs: [
{
label: 'Ford',
subs: []
},
{
label: 'Chevrolet',
subs: []
}
]
}
]
},
{
label: 'Sedan',
subs: []
}
]
}
采纳答案by Peter Olson
You can create a recursive function like this to do a depth-first traversal of the cars
object.
您可以创建这样的递归函数来对cars
对象进行深度优先遍历。
var findObjectByLabel = function(obj, label) {
if(obj.label === label) { return obj; }
for(var i in obj) {
if(obj.hasOwnProperty(i)){
var foundLabel = findObjectByLabel(obj[i], label);
if(foundLabel) { return foundLabel; }
}
}
return null;
};
which can be called like so
可以这样称呼
findObjectByLabel(car, "Chevrolet");
回答by Menelaos Kotsollaris
In case you want to deeply iterateinto a complex (nested) object for each key & value, you can do so using Object.keys(), recursively:
如果你想深深迭代到一个复杂的(嵌套)对象为每个键和值,你可以这样做使用Object.keys() ,递归:
const iterate = (obj) => {
Object.keys(obj).forEach(key => {
console.log(`key: ${key}, value: ${obj[key]}`)
if (typeof obj[key] === 'object') {
iterate(obj[key])
}
})
}
回答by Hyman Giffin
Here is a dead-simple method using only 3 variables, only 9 lines of code, and no recursion.
这是一个非常简单的方法,只使用了 3 个变量,只有 9 行代码,而且没有递归。
function forEachNested(O, f, cur){
O = [ O ]; // ensure that f is called with the top-level object
while (O.length) // keep on processing the top item on the stack
if(
!f( cur = O.pop() ) && // do not spider down if `f` returns true
cur instanceof Object && // ensure cur is an object, but not null
[Object, Array].includes(cur.constructor) //limit search to [] and {}
) O.push.apply(O, Object.values(cur)); //search all values deeper inside
}
To use the above function, pass the array as the first argument and the callback function as the second argument. The callback function will receive 1 argument when called: the current item being iterated.
要使用上述函数,请将数组作为第一个参数传递,将回调函数作为第二个参数传递。回调函数在调用时将接收 1 个参数:正在迭代的当前项目。
(function(){"use strict";
var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};
var lookForCar = prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();
var foundObject = null;
forEachNested(cars, function(currentValue){
if(currentValue.constructor === Object &&
currentValue.label.toLowerCase() === lookForCar) {
foundObject = currentValue;
}
});
if (foundObject !== null) {
console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
} else {
console.log('Nothing found with a label of "' + lookForCar + '" :(');
}
function forEachNested(O, f, cur){
O = [ O ]; // ensure that f is called with the top-level object
while (O.length) // keep on processing the top item on the stack
if(
!f( cur = O.pop() ) && // do not spider down if `f` returns true
cur instanceof Object && // ensure cur is an object, but not null
[Object, Array].includes(cur.constructor) //limit search to [] and {}
) O.push.apply(O, Object.values(cur)); //search all values deeper inside
}
})();
A "cheat" alternative might be to use JSON.stringify
to iterate. HOWEVER, JSON.stringify
will call the toString
method of each object it passes over, which may produce undesirable results if you have your own special uses for the toString
.
“作弊”替代方案可能是JSON.stringify
用于迭代。但是,JSON.stringify
将调用toString
它传递的每个对象的方法,如果您对toString
.
function forEachNested(O, f, v){
typeof O === "function" ? O(v) : JSON.stringify(O,forEachNested.bind(0,f));
return v; // so that JSON.stringify keeps on recursing
}
(function(){"use strict";
var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};
var lookForCar = prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();
var foundObject = null;
forEachNested(cars, function(currentValue){
if(currentValue.constructor === Object &&
currentValue.label.toLowerCase() === lookForCar) {
foundObject = currentValue;
}
});
if (foundObject !== null)
console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
else
console.log('Nothing found with a label of "' + lookForCar + '" :(');
function forEachNested(O, f, v){
typeof O === "function" ? O(v) : JSON.stringify(O,forEachNested.bind(0,f));
return v; // so that JSON.stringify keeps on recursing
}
})();
However, while the above method might be useful for demonstration purposes, Object.values
is not supported by Internet Explorer and there are many terribly illperformant places in the code:
然而,虽然上述方法对于演示目的可能有用,Object.values
但 Internet Explorer 不支持,并且代码中有许多非常糟糕的地方:
- the code changes the value of input parameters (arguments) [lines 2 & 5],
- the code calls
Array.prototype.push
andArray.prototype.pop
on every single item [lines 5 & 8], - the code only does a pointer-comparison for the constructor which does not work on out-of-window objects [line 7],
- the code duplicates the array returned from
Object.values
[line 8], - the code does not localize
window.Object
orwindow.Object.values
[line 9], - and the code needlessly calls Object.values on arrays [line 8].
- 代码更改了输入参数的值(参数)[第 2 行和第 5 行],
- 代码在每个项目上调用
Array.prototype.push
和Array.prototype.pop
[第 5 行和第 8 行], - 代码只对构造函数进行指针比较,它不适用于窗口外对象 [第 7 行],
- 代码复制了从
Object.values
[第 8 行]返回的数组, - 代码未本地化
window.Object
或window.Object.values
[第 9 行], - 并且代码不必要地在数组上调用 Object.values [第 8 行]。
Below is a much much faster version that should be far faster than any other solution. The solution below fixes all of the performance problems listed above. However, it iterates in a much different way: it iterates all the arrays first, then iterates all the objects. It continues to iterate its present type until complete exhaustion including iteration subvalues inside the current list of the current flavor being iterated. Then, the function iterates all of the other type. By iterating until exhaustion before switching over, the iteration loop gets hotter than otherwise and iterates even faster. This method also comes with an added advantage: the callback which is called on each value gets passed a second parameter. This second parameter is the array returned from Object.values
called on the parent hash Object, or the parent Array itself.
下面是一个快得多的版本,应该比任何其他解决方案都快得多。下面的解决方案修复了上面列出的所有性能问题。然而,它以一种截然不同的方式进行迭代:它首先迭代所有数组,然后迭代所有对象。它继续迭代其当前类型,直到完全耗尽,包括正在迭代的当前风味的当前列表中的迭代子值。然后,该函数迭代所有其他类型。通过在切换之前迭代直到耗尽,迭代循环变得比其他方式更热并且迭代得更快。这个方法还有一个额外的好处:对每个值调用的回调函数传递了第二个参数。第二个参数是从返回的数组Object.values
在父哈希对象或父数组本身上调用。
var getValues = Object.values; // localize
var type_toString = Object.prototype.toString;
function forEachNested(objectIn, functionOnEach){
"use strict";
functionOnEach( objectIn );
// for iterating arbitrary objects:
var allLists = [ ];
if (type_toString.call( objectIn ) === '[object Object]')
allLists.push( getValues(objectIn) );
var allListsSize = allLists.length|0; // the length of allLists
var indexLists = 0;
// for iterating arrays:
var allArray = [ ];
if (type_toString.call( objectIn ) === '[object Array]')
allArray.push( objectIn );
var allArraySize = allArray.length|0; // the length of allArray
var indexArray = 0;
do {
// keep cycling back and forth between objects and arrays
for ( ; indexArray < allArraySize; indexArray=indexArray+1|0) {
var currentArray = allArray[indexArray];
var currentLength = currentArray.length;
for (var curI=0; curI < currentLength; curI=curI+1|0) {
var arrayItemInner = currentArray[curI];
if (arrayItemInner === undefined &&
!currentArray.hasOwnProperty(arrayItemInner)) {
continue; // the value at this position doesn't exist!
}
functionOnEach(arrayItemInner, currentArray);
if (typeof arrayItemInner === 'object') {
var typeTag = type_toString.call( arrayItemInner );
if (typeTag === '[object Object]') {
// Array.prototype.push returns the new length
allListsSize=allLists.push( getValues(arrayItemInner) );
} else if (typeTag === '[object Array]') {
allArraySize=allArray.push( arrayItemInner );
}
}
}
allArray[indexArray] = null; // free up memory to reduce overhead
}
for ( ; indexLists < allListsSize; indexLists=indexLists+1|0) {
var currentList = allLists[indexLists];
var currentLength = currentList.length;
for (var curI=0; curI < currentLength; curI=curI+1|0) {
var listItemInner = currentList[curI];
functionOnEach(listItemInner, currentList);
if (typeof listItemInner === 'object') {
var typeTag = type_toString.call( listItemInner );
if (typeTag === '[object Object]') {
// Array.prototype.push returns the new length
allListsSize=allLists.push( getValues(listItemInner) );
} else if (typeTag === '[object Array]') {
allArraySize=allArray.push( listItemInner );
}
}
}
allLists[indexLists] = null; // free up memory to reduce overhead
}
} while (indexLists < allListsSize || indexArray < allArraySize);
}
(function(){"use strict";
var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};
var lookForCar = prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();
var getValues = Object.values; // localize
var type_toString = Object.prototype.toString;
function forEachNested(objectIn, functionOnEach){
functionOnEach( objectIn );
// for iterating arbitrary objects:
var allLists = [ ];
if (type_toString.call( objectIn ) === '[object Object]')
allLists.push( getValues(objectIn) );
var allListsSize = allLists.length|0; // the length of allLists
var indexLists = 0;
// for iterating arrays:
var allArray = [ ];
if (type_toString.call( objectIn ) === '[object Array]')
allArray.push( objectIn );
var allArraySize = allArray.length|0; // the length of allArray
var indexArray = 0;
do {
// keep cycling back and forth between objects and arrays
for ( ; indexArray < allArraySize; indexArray=indexArray+1|0) {
var currentArray = allArray[indexArray];
var currentLength = currentArray.length;
for (var curI=0; curI < currentLength; curI=curI+1|0) {
var arrayItemInner = currentArray[curI];
if (arrayItemInner === undefined &&
!currentArray.hasOwnProperty(arrayItemInner)) {
continue; // the value at this position doesn't exist!
}
functionOnEach(arrayItemInner, currentArray);
if (typeof arrayItemInner === 'object') {
var typeTag = type_toString.call( arrayItemInner );
if (typeTag === '[object Object]') {
// Array.prototype.push returns the new length
allListsSize=allLists.push( getValues(arrayItemInner) );
} else if (typeTag === '[object Array]') {
allArraySize=allArray.push( arrayItemInner );
}
}
}
allArray[indexArray] = null; // free up memory to reduce overhead
}
for ( ; indexLists < allListsSize; indexLists=indexLists+1|0) {
var currentList = allLists[indexLists];
var currentLength = currentList.length;
for (var curI=0; curI < currentLength; curI=curI+1|0) {
var listItemInner = currentList[curI];
functionOnEach(listItemInner, currentList);
if (typeof listItemInner === 'object') {
var typeTag = type_toString.call( listItemInner );
if (typeTag === '[object Object]') {
// Array.prototype.push returns the new length
allListsSize=allLists.push( getValues(listItemInner) );
} else if (typeTag === '[object Array]') {
allArraySize=allArray.push( listItemInner );
}
}
}
allLists[indexLists] = null; // free up memory to reduce overhead
}
} while (indexLists < allListsSize || indexArray < allArraySize);
}
var foundObject = null;
forEachNested(cars, function(currentValue){
if(currentValue.constructor === Object &&
currentValue.label.toLowerCase() === lookForCar) {
foundObject = currentValue;
}
});
if (foundObject !== null) {
console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
} else {
console.log('Nothing found with a label of "' + lookForCar + '" :(');
}
})();
If you have a problem with circular references (e.g. having object A's values being object A itself in such as that object A contains itself), or you just need the keys then the following slower solution is available.
如果您在循环引用方面遇到问题(例如,对象 A 的值是对象 A 本身,例如对象 A 包含自身),或者您只需要键,那么可以使用以下较慢的解决方案。
function forEachNested(O, f){
O = Object.entries(O);
var cur;
function applyToEach(x){return cur[1][x[0]] === x[1]}
while (O.length){
cur = O.pop();
f(cur[0], cur[1]);
if (typeof cur[1] === 'object' && cur[1].constructor === Object &&
!O.some(applyToEach))
O.push.apply(O, Object.entries(cur[1]));
}
}
Because these methods do not use any recursion of any sort, these functions are well suited for areas where you might have thousands of levels of depth. The stack limit varies greatly from browser to browser, so recursion to an unknown depth is not very wise in Javascript.
因为这些方法不使用任何类型的递归,所以这些函数非常适合可能有数千个深度级别的领域。堆栈限制因浏览器而异,因此在 Javascript 中递归到未知深度并不是很明智。
回答by James Clark
The following code assumes no circular references, and assumes subs
is always an array (and not null in leaf nodes):
以下代码假定没有循环引用,并且假定subs
始终是一个数组(并且在叶节点中不为空):
function find(haystack, needle) {
if (haystack.label === needle) return haystack;
for (var i = 0; i < haystack.subs.length; i ++) {
var result = find(haystack.subs[i], needle);
if (result) return result;
}
return null;
}
回答by Jasvir
You can get through every object in the list and get which value you want. Just pass an object as first parameter in the function call and object property which you want as second parameter. Change object with your object.
您可以遍历列表中的每个对象并获取您想要的值。只需将对象作为函数调用中的第一个参数传递,并将对象属性作为第二个参数传递。用你的对象改变对象。
const treeData = [{
"jssType": "fieldset",
"jssSelectLabel": "Fieldset (with legend)",
"jssSelectGroup": "jssItem",
"jsName": "fieldset-715",
"jssLabel": "Legend",
"jssIcon": "typcn typcn-folder",
"expanded": true,
"children": [{
"jssType": "list-ol",
"jssSelectLabel": "List - ol",
"jssSelectGroup": "jssItem",
"jsName": "list-ol-147",
"jssLabel": "",
"jssIcon": "dashicons dashicons-editor-ol",
"noChildren": false,
"expanded": true,
"children": [{
"jssType": "list-li",
"jssSelectLabel": "List Item - li",
"jssSelectGroup": "jssItem",
"jsName": "list-li-752",
"jssLabel": "",
"jssIcon": "dashicons dashicons-editor-ul",
"noChildren": false,
"expanded": true,
"children": [{
"jssType": "text",
"jssSelectLabel": "Text (short text)",
"jssSelectGroup": "jsTag",
"jsName": "text-422",
"jssLabel": "Your Name (required)",
"jsRequired": true,
"jsTagOptions": [{
"jsOption": "",
"optionLabel": "Default value",
"optionType": "input"
},
{
"jsOption": "placeholder",
"isChecked": false,
"optionLabel": "Use this text as the placeholder of the field",
"optionType": "checkbox"
},
{
"jsOption": "akismet_author_email",
"isChecked": false,
"optionLabel": "Akismet - this field requires author's email address",
"optionType": "checkbox"
}
],
"jsValues": "",
"jsPlaceholder": false,
"jsAkismetAuthor": false,
"jsIdAttribute": "",
"jsClassAttribute": "",
"jssIcon": "typcn typcn-sort-alphabetically",
"noChildren": true
}]
},
{
"jssType": "list-li",
"jssSelectLabel": "List Item - li",
"jssSelectGroup": "jssItem",
"jsName": "list-li-538",
"jssLabel": "",
"jssIcon": "dashicons dashicons-editor-ul",
"noChildren": false,
"expanded": true,
"children": [{
"jssType": "email",
"jssSelectLabel": "Email",
"jssSelectGroup": "jsTag",
"jsName": "email-842",
"jssLabel": "Email Address (required)",
"jsRequired": true,
"jsTagOptions": [{
"jsOption": "",
"optionLabel": "Default value",
"optionType": "input"
},
{
"jsOption": "placeholder",
"isChecked": false,
"optionLabel": "Use this text as the placeholder of the field",
"optionType": "checkbox"
},
{
"jsOption": "akismet_author_email",
"isChecked": false,
"optionLabel": "Akismet - this field requires author's email address",
"optionType": "checkbox"
}
],
"jsValues": "",
"jsPlaceholder": false,
"jsAkismetAuthorEmail": false,
"jsIdAttribute": "",
"jsClassAttribute": "",
"jssIcon": "typcn typcn-mail",
"noChildren": true
}]
},
{
"jssType": "list-li",
"jssSelectLabel": "List Item - li",
"jssSelectGroup": "jssItem",
"jsName": "list-li-855",
"jssLabel": "",
"jssIcon": "dashicons dashicons-editor-ul",
"noChildren": false,
"expanded": true,
"children": [{
"jssType": "textarea",
"jssSelectLabel": "Textarea (long text)",
"jssSelectGroup": "jsTag",
"jsName": "textarea-217",
"jssLabel": "Your Message",
"jsRequired": false,
"jsTagOptions": [{
"jsOption": "",
"optionLabel": "Default value",
"optionType": "input"
},
{
"jsOption": "placeholder",
"isChecked": false,
"optionLabel": "Use this text as the placeholder of the field",
"optionType": "checkbox"
}
],
"jsValues": "",
"jsPlaceholder": false,
"jsIdAttribute": "",
"jsClassAttribute": "",
"jssIcon": "typcn typcn-document-text",
"noChildren": true
}]
}
]
},
{
"jssType": "paragraph",
"jssSelectLabel": "Paragraph - p",
"jssSelectGroup": "jssItem",
"jsName": "paragraph-993",
"jssContent": "* Required",
"jssIcon": "dashicons dashicons-editor-paragraph",
"noChildren": true
}
]
},
{
"jssType": "submit",
"jssSelectLabel": "Submit",
"jssSelectGroup": "jsTag",
"jsName": "submit-704",
"jssLabel": "Send",
"jsValues": "",
"jsRequired": false,
"jsIdAttribute": "",
"jsClassAttribute": "",
"jssIcon": "typcn typcn-mail",
"noChildren": true
},
];
function findObjectByLabel(obj, label) {
for(var elements in obj){
if (elements === label){
console.log(obj[elements]);
}
if(typeof obj[elements] === 'object'){
findObjectByLabel(obj[elements], 'jssType');
}
}
};
findObjectByLabel(treeData, 'jssType');
回答by ChiralMichael
Here is a concise breadth-first iterative solution, which I prefer to recursion:
这是一个简洁的广度优先迭代解决方案,我更喜欢递归:
const findCar = function(car) {
const carSearch = [cars];
while(carSearch.length) {
let item = carSearch.shift();
if (item.label === car) return true;
carSearch.push(...item.subs);
}
return false;
}
回答by Jo?o Pimentel Ferreira
You can have a recursive function with a parse function built within it.
你可以有一个递归函数,其中内置了一个解析函数。
Here how it works
这是它是如何工作的
// recursively loops through nested object and applys parse function
function parseObjectProperties(obj, parse) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
parseObjectProperties(obj[k], parse)
} else if (obj.hasOwnProperty(k)) {
parse(obj, k)
}
}
}
//**************
// example
var foo = {
bar:'a',
child:{
b: 'b',
grand:{
greatgrand: {
c:'c'
}
}
}
}
// just console properties
parseObjectProperties(foo, function(obj, prop) {
console.log(prop + ':' + obj[prop])
})
// add character a on every property
parseObjectProperties(foo, function(obj, prop) {
obj[prop] += 'a'
})
console.log(foo)
回答by Mansour
The following snippet will iterate over nested objects. Objects within the objects. Feel free to change it to meet your requirements. Like if you want to add array support make if-else and make a function that loop through arrays ...
以下代码段将迭代嵌套对象。对象中的对象。随意更改它以满足您的要求。就像如果你想添加数组支持 make if-else 并创建一个循环数组的函数......
var p = {
"p1": "value1",
"p2": "value2",
"p3": "value3",
"p4": {
"p4": 'value 4'
}
};
/**
* Printing a nested javascript object
*/
function jsonPrinter(obj) {
for (let key in obj) {
// checking if it's nested
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
jsonPrinter(obj[key])
} else {
// printing the flat attributes
console.log(key + " -> " + obj[key]);
}
}
}
jsonPrinter(p);
回答by VadimB
To increase performance for further tree manipulation is good to transform tree view into line collection view, like [obj1, obj2, obj3]. You can store parent-child object relations to easy navigate to parent/child scope.
为了提高进一步操作树的性能,最好将树视图转换为行集合视图,例如 [obj1, obj2, obj3]。您可以存储父子对象关系以轻松导航到父/子范围。
Searching element inside collection is more efficient then find element inside tree (recursion, addition dynamic function creation, closure).
在集合中搜索元素比在树中查找元素更有效(递归、添加动态函数创建、闭包)。
回答by vikyd
modify from Peter Olson's answer: https://stackoverflow.com/a/8085118
从Peter Olson的回答中修改:https: //stackoverflow.com/a/8085118
- can avoid string value
!obj || (typeof obj === 'string'
- can custom your key
- 可以避免字符串值
!obj || (typeof obj === 'string'
- 可以自定义您的密钥
var findObjectByKeyVal= function (obj, key, val) {
if (!obj || (typeof obj === 'string')) {
return null
}
if (obj[key] === val) {
return obj
}
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
var found = findObjectByKeyVal(obj[i], key, val)
if (found) {
return found
}
}
}
return null
}