Итерация через вложенные объекты 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" этого объекта. Это может вызвать проблему, хотя, если у вас очень глубокая структура объектов