Итерация через вложенные объекты JavaScript
Я пытаюсь перебрать вложенный объект, чтобы получить конкретный объект, идентифицированный строкой. В приведенном ниже примере объекта строка идентификатора является свойством "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: []
}
]
}
Ответы
Ответ 1
Вы можете создать такую рекурсивную функцию, чтобы сделать обход глубины объекта 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");
Ответ 2
Если вы хотите выполнить глубокую итерацию в сложный (вложенный) объект для каждого ключа и значения, вы можете сделать это с помощью 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])
}
})
}
Пример REPL.
Ответ 3
Следующий код не предполагает циклических ссылок и предполагает, что 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;
}
Ответ 4
Вот простой метод, использующий только 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
}
})();
Ответ 5
Вот краткое итерационное решение в ширину, которое я предпочитаю рекурсии:
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;
}
Ответ 6
Чтобы повысить производительность для дальнейшей обработки дерева, полезно преобразовать древовидную структуру в представление коллекции строк, например [obj1, obj2, obj3]. Вы можете сохранить отношения между родительскими и дочерними объектами, чтобы легко перейти к области родительского/дочернего объектов.
Поиск элемента внутри коллекции более эффективен, чем поиск элемента внутри дерева (рекурсия, добавление динамических функций, закрытие).
Ответ 7
изменить ответ Питера Олсона: fooobar.com/questions/49510/...
- может избежать строкового значения
!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
}
Ответ 8
Следующий фрагмент будет перебирать вложенные объекты. Объекты внутри объектов. Не стесняйтесь изменять его в соответствии с вашими требованиями. Например, если вы хотите добавить поддержку массивов 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 nested
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
jsonPrinter(obj[key])
} else {
// printing the flat attributes
console.log(key + " -> " + obj[key]);
}
}
}
jsonPrinter(p);
Ответ 9
Вы можете пройти через каждый объект в списке и получить желаемое значение. Просто передайте объект в качестве первого параметра в вызове функции, а свойство объекта, которое вы хотите, в качестве второго параметра. Изменить объект с вашим объектом.
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 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 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');
Ответ 10
Вы можете иметь рекурсивную функцию со встроенной функцией синтаксического анализа.
Вот как это работает
// 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)
Ответ 11
Я сделал метод выбора, как выбор Лодаша. Это не совсем хорошо, как 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;
а вот ссылка на живой пример с юнит-тестами
Ответ 12
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
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
Попробуйте что-то вроде этого:
cars.subs[0].subs[0].label
Если вы хотите на самом деле итерации, вы можете сделать цикл for для каждого объекта в рекурсивной функции и вызвать эту рекурсивную функцию каждый раз, когда вы нажимаете свойство "subs" этого объекта. Это может вызвать проблему, хотя, если у вас очень глубокая структура объектов