我正在尝试遍历嵌套对象以检索由字符串标识的特定对象。在下面的示例对象中,标识符字符串是“label”属性。我无法绕过如何遍历树以返回适当的对象。任何帮助或建议将不胜感激。
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: []
}
]
}
答案 0 :(得分:44)
您可以创建这样的递归函数来对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;
};
可以像这样调用
findObjectByLabel(car, "Chevrolet");
答案 1 :(得分:10)
如果您想针对每个键和值 深度迭代到复杂的(嵌套的)对象中,则可以使用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])
}
})
}
答案 2 :(得分:3)
以下代码假定没有循环引用,并假设subs
始终是一个数组(在叶节点中不为null):
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;
}
答案 3 :(得分:3)
这是一个简单的方法,只使用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
}
要使用上述函数,请将数组作为第一个参数传递,将回调函数作为第二个参数传递。调用时,回调函数将接收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
}
})();
“作弊”替代方案可能是使用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
}
})();
但是,虽然上述方法可能对演示目的有用,但Internet Explorer不支持Object.values
,并且代码中存在许多非常不合理的地方:
Array.prototype.push
和Array.prototype.pop
[第5行和第5行] 8] Object.values
[第8行],window.Object
或window.Object.values
[第9行],下面是一个快得多的版本,应该比任何其他解决方案快得多。但是,它以一种非常不同的方式迭代:它首先迭代所有数组,然后迭代所有对象。它继续迭代其当前类型,直到完全耗尽,包括迭代当前列表的当前列表内的迭代子值。然后,该函数迭代所有其他类型。通过在切换之前迭代直到耗尽,迭代循环变得比其他情况更热并且迭代更快。此方法还带来了额外的优势:在每个值上调用的回调将传递第二个参数。第二个参数是从父哈希对象上调用的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 + '" :(');
}
})();
如果您对循环引用有问题(例如,对象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]));
}
}
因为这些方法不使用任何类型的任何递归,所以这些函数非常适合于可能具有数千个深度级别的区域。 The stack limit varies greatly from browser to browser,因此在Javascript中递归到未知深度并不是很明智。
答案 4 :(得分:2)
这是使用 object-scan
的解决方案// const objectScan = require('object-scan');
const 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: [] } ] };
const find = (haystack, label) => objectScan(['**.label'], {
filterFn: ({ value }) => value === label,
rtn: 'parent',
abort: true
})(haystack);
console.log(find(cars, 'Sedan'));
// => { label: 'Sedan', subs: [] }
console.log(find(cars, 'SUVs'));
// => { label: 'SUVs', subs: [] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>
免责声明:我是object-scan
的作者答案 5 :(得分:1)
为了提高进一步树操作的性能,最好将树视图转换为线集合视图,如[obj1,obj2,obj3]。您可以存储父子对象关系,以便轻松导航到父/子范围。
在集合中搜索元素比在树中查找元素(递归,添加动态函数创建,闭包)更有效。
答案 6 :(得分:1)
从 Peter Olson 的回答中修改:https://stackoverflow.com/a/8085118
!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
}
答案 7 :(得分:1)
以下代码段将迭代嵌套对象。对象中的对象。随时更改它以满足您的要求。就像您想添加数组支持一样,使用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);
答案 8 :(得分:1)
您可以遍历列表中的每个对象并获取所需的值。只需将一个对象作为您要作为第二个参数的函数调用和对象属性中的第一个参数传递即可。用您的对象更改对象。
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');
答案 9 :(得分:1)
您可以具有内置了解析功能的递归功能。
这里是如何工作的
// 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)
答案 10 :(得分:1)
这里是一个简洁的广度优先的迭代解决方案,我更喜欢递归:
const findCar = function(car) {
const carSearch = [cars];
while(carSearch.length) {
let item = carSearch.pop();
if (item.label === car) return true;
carSearch.push(...item.subs);
}
return false;
}
答案 11 :(得分:0)
我做了一个类似lodash pick的选择方法。它不像lodash _.pick一样好,但是您可以选择任何嵌套的属性事件。
例如:
let car = { name: 'BMW', meta: { model: 2018, color: 'white'};
pick(car,['name','model']) // Output will be {name: 'BMW', model: 2018}
代码:
const pick = (object, props) => {
let newObject = {};
if (isObjectEmpty(object)) return {}; // Object.keys(object).length <= 0;
for (let i = 0; i < props.length; i++) {
Object.keys(object).forEach(key => {
if (key === props[i] && object.hasOwnProperty(props[i])) {
newObject[key] = object[key];
} else if (typeof object[key] === "object") {
Object.assign(newObject, pick(object[key], [props[i]]));
}
});
}
return newObject;
};
function isObjectEmpty(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
export default pick;
这是link to live example,带有单元测试
答案 12 :(得分:0)
var findObjectByLabel = function(objs, label) {
if(objs.label === label) {
return objs;
}
else{
if(objs.subs){
for(var i in objs.subs){
let found = findObjectByLabel(objs.subs[i],label)
if(found) return found
}
}
}
};
findObjectByLabel(cars, "Ford");
答案 13 :(得分:0)
var findObjectByLabel = function(obj, label)
{
var foundLabel=null;
if(obj.label === label)
{
return obj;
}
for(var i in obj)
{
if(Array.isArray(obj[i])==true)
{
for(var j=0;j<obj[i].length;j++)
{
foundLabel = findObjectByLabel(obj[i], label);
}
}
else if(typeof(obj[i]) == 'object')
{
if(obj.hasOwnProperty(i))
{
foundLabel = findObjectByLabel(obj[i], label);
}
}
if(foundLabel)
{
return foundLabel;
}
}
return null;
};
var x = findObjectByLabel(cars, "Sedan");
alert(JSON.stringify(x));
答案 14 :(得分:0)
在带有对象/通用方式的打字稿中,也可以将其实现:
export interface INestedIterator<T> {
getChildren(): T[];
}
export class NestedIterator {
private static forEach<T extends INestedIterator<T>>(obj: T, fn: ((obj: T) => void)): void {
fn(obj);
if (obj.getChildren().length) {
for (const item of obj.getChildren()) {
NestedIterator.forEach(item, fn);
};
}
}
}
比您可以实现接口INestedIterator<T>
:
class SomeNestedClass implements INestedIterator<SomeNestedClass>{
items: SomeNestedClass[];
getChildren() {
return this.items;
}
}
然后打电话给
NestedIterator.forEach(someNesteObject, (item) => {
console.log(item);
})
如果您不想使用接口和强类型的类,只需删除类型
export class NestedIterator {
private static forEach(obj: any, fn: ((obj: any) => void)): void {
fn(obj);
if (obj.items && obj.items.length) {
for (const item of obj.items) {
NestedIterator.forEach(item, fn);
};
}
}
}