[javascript] Iterate through Nested JavaScript Objects

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: []
    }
  ]
}

This question is related to javascript iteration

The answer is


The following code assumes no circular references, and assumes subs is always an array (and not null in leaf nodes):

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;
}

In case you want to deeply iterate into a complex (nested) object for each key & value, you can do so using Object.keys(), recursively:

const iterate = (obj) => {
    Object.keys(obj).forEach(key => {

    console.log(`key: ${key}, value: ${obj[key]}`)

    if (typeof obj[key] === 'object') {
            iterate(obj[key])
        }
    })
}

REPL example.


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));

You can have a recursive function with a parse function built within it.

Here how it works

_x000D_
_x000D_
// recursively loops through nested object and applys parse function_x000D_
function parseObjectProperties(obj, parse) {_x000D_
  for (var k in obj) {_x000D_
    if (typeof obj[k] === 'object' && obj[k] !== null) {_x000D_
      parseObjectProperties(obj[k], parse)_x000D_
    } else if (obj.hasOwnProperty(k)) {_x000D_
      parse(obj, k)_x000D_
    }_x000D_
  }_x000D_
}_x000D_
//**************_x000D_
_x000D_
_x000D_
// example_x000D_
var foo = {_x000D_
  bar:'a',_x000D_
  child:{_x000D_
    b: 'b',_x000D_
    grand:{_x000D_
      greatgrand: {_x000D_
        c:'c'_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
}_x000D_
_x000D_
_x000D_
// just console properties_x000D_
parseObjectProperties(foo, function(obj, prop) {_x000D_
  console.log(prop + ':' + obj[prop])_x000D_
})_x000D_
_x000D_
// add character a on every property_x000D_
parseObjectProperties(foo, function(obj, prop) {_x000D_
  obj[prop] += 'a'_x000D_
})_x000D_
console.log(foo)
_x000D_
_x000D_
_x000D_


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 ...

_x000D_
_x000D_
var p = {_x000D_
    "p1": "value1",_x000D_
    "p2": "value2",_x000D_
    "p3": "value3",_x000D_
    "p4": {_x000D_
        "p4": 'value 4'_x000D_
    }_x000D_
};_x000D_
_x000D_
_x000D_
_x000D_
/**_x000D_
*   Printing a nested javascript object_x000D_
*/_x000D_
function jsonPrinter(obj) {_x000D_
_x000D_
    for (let key in obj) {_x000D_
        // checking if it's nested_x000D_
        if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {_x000D_
            jsonPrinter(obj[key])_x000D_
        } else {_x000D_
            // printing the flat attributes_x000D_
            console.log(key + " -> " + obj[key]);_x000D_
        }_x000D_
    }_x000D_
}_x000D_
_x000D_
jsonPrinter(p);
_x000D_
_x000D_
_x000D_


I made a pick method like lodash pick. It is not exactly good like lodash _.pick, but you can pick any property event any nested property.

  • You just have to pass your object as a first argument then an array of properties in which you want to get their value as a second argument.

for example:

let car = { name: 'BMW', meta: { model: 2018, color: 'white'};
pick(car,['name','model']) // Output will be {name: 'BMW', model: 2018}

Code :

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;

and here is the link to live example with unit tests


In typescript with object/generic way, it could be alse implemented:

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);            
            };
        }
    }
}

than you can implement interface INestedIterator<T>:

class SomeNestedClass implements INestedIterator<SomeNestedClass>{
    items: SomeNestedClass[];
    getChildren() {
        return this.items;
    }
}

and then just call

NestedIterator.forEach(someNesteObject, (item) => {
    console.log(item);
})

if you don't want use interfaces and strongly typed classes, just remove types

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);            
            };
        }
    }
}

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.

_x000D_
_x000D_
const treeData = [{_x000D_
        "jssType": "fieldset",_x000D_
        "jssSelectLabel": "Fieldset (with legend)",_x000D_
        "jssSelectGroup": "jssItem",_x000D_
        "jsName": "fieldset-715",_x000D_
        "jssLabel": "Legend",_x000D_
        "jssIcon": "typcn typcn-folder",_x000D_
        "expanded": true,_x000D_
        "children": [{_x000D_
                "jssType": "list-ol",_x000D_
                "jssSelectLabel": "List - ol",_x000D_
                "jssSelectGroup": "jssItem",_x000D_
                "jsName": "list-ol-147",_x000D_
                "jssLabel": "",_x000D_
                "jssIcon": "dashicons dashicons-editor-ol",_x000D_
                "noChildren": false,_x000D_
                "expanded": true,_x000D_
                "children": [{_x000D_
                        "jssType": "list-li",_x000D_
                        "jssSelectLabel": "List Item - li",_x000D_
                        "jssSelectGroup": "jssItem",_x000D_
                        "jsName": "list-li-752",_x000D_
                        "jssLabel": "",_x000D_
                        "jssIcon": "dashicons dashicons-editor-ul",_x000D_
                        "noChildren": false,_x000D_
                        "expanded": true,_x000D_
                        "children": [{_x000D_
                            "jssType": "text",_x000D_
                            "jssSelectLabel": "Text (short text)",_x000D_
                            "jssSelectGroup": "jsTag",_x000D_
                            "jsName": "text-422",_x000D_
                            "jssLabel": "Your Name (required)",_x000D_
                            "jsRequired": true,_x000D_
                            "jsTagOptions": [{_x000D_
                                    "jsOption": "",_x000D_
                                    "optionLabel": "Default value",_x000D_
                                    "optionType": "input"_x000D_
                                },_x000D_
                                {_x000D_
                                    "jsOption": "placeholder",_x000D_
                                    "isChecked": false,_x000D_
                                    "optionLabel": "Use this text as the placeholder of the field",_x000D_
                                    "optionType": "checkbox"_x000D_
                                },_x000D_
                                {_x000D_
                                    "jsOption": "akismet_author_email",_x000D_
                                    "isChecked": false,_x000D_
                                    "optionLabel": "Akismet - this field requires author's email address",_x000D_
                                    "optionType": "checkbox"_x000D_
                                }_x000D_
                            ],_x000D_
                            "jsValues": "",_x000D_
                            "jsPlaceholder": false,_x000D_
                            "jsAkismetAuthor": false,_x000D_
                            "jsIdAttribute": "",_x000D_
                            "jsClassAttribute": "",_x000D_
                            "jssIcon": "typcn typcn-sort-alphabetically",_x000D_
                            "noChildren": true_x000D_
                        }]_x000D_
                    },_x000D_
                    {_x000D_
                        "jssType": "list-li",_x000D_
                        "jssSelectLabel": "List Item - li",_x000D_
                        "jssSelectGroup": "jssItem",_x000D_
                        "jsName": "list-li-538",_x000D_
                        "jssLabel": "",_x000D_
                        "jssIcon": "dashicons dashicons-editor-ul",_x000D_
                        "noChildren": false,_x000D_
                        "expanded": true,_x000D_
                        "children": [{_x000D_
                            "jssType": "email",_x000D_
                            "jssSelectLabel": "Email",_x000D_
                            "jssSelectGroup": "jsTag",_x000D_
                            "jsName": "email-842",_x000D_
                            "jssLabel": "Email Address (required)",_x000D_
                            "jsRequired": true,_x000D_
                            "jsTagOptions": [{_x000D_
                                    "jsOption": "",_x000D_
                                    "optionLabel": "Default value",_x000D_
                                    "optionType": "input"_x000D_
                                },_x000D_
                                {_x000D_
                                    "jsOption": "placeholder",_x000D_
                                    "isChecked": false,_x000D_
                                    "optionLabel": "Use this text as the placeholder of the field",_x000D_
                                    "optionType": "checkbox"_x000D_
                                },_x000D_
                                {_x000D_
                                    "jsOption": "akismet_author_email",_x000D_
                                    "isChecked": false,_x000D_
                                    "optionLabel": "Akismet - this field requires author's email address",_x000D_
                                    "optionType": "checkbox"_x000D_
                                }_x000D_
                            ],_x000D_
                            "jsValues": "",_x000D_
                            "jsPlaceholder": false,_x000D_
                            "jsAkismetAuthorEmail": false,_x000D_
                            "jsIdAttribute": "",_x000D_
                            "jsClassAttribute": "",_x000D_
                            "jssIcon": "typcn typcn-mail",_x000D_
                            "noChildren": true_x000D_
                        }]_x000D_
                    },_x000D_
                    {_x000D_
                        "jssType": "list-li",_x000D_
                        "jssSelectLabel": "List Item - li",_x000D_
                        "jssSelectGroup": "jssItem",_x000D_
                        "jsName": "list-li-855",_x000D_
                        "jssLabel": "",_x000D_
                        "jssIcon": "dashicons dashicons-editor-ul",_x000D_
                        "noChildren": false,_x000D_
                        "expanded": true,_x000D_
                        "children": [{_x000D_
                            "jssType": "textarea",_x000D_
                            "jssSelectLabel": "Textarea (long text)",_x000D_
                            "jssSelectGroup": "jsTag",_x000D_
                            "jsName": "textarea-217",_x000D_
                            "jssLabel": "Your Message",_x000D_
                            "jsRequired": false,_x000D_
                            "jsTagOptions": [{_x000D_
                                    "jsOption": "",_x000D_
                                    "optionLabel": "Default value",_x000D_
                                    "optionType": "input"_x000D_
                                },_x000D_
                                {_x000D_
                                    "jsOption": "placeholder",_x000D_
                                    "isChecked": false,_x000D_
                                    "optionLabel": "Use this text as the placeholder of the field",_x000D_
                                    "optionType": "checkbox"_x000D_
                                }_x000D_
                            ],_x000D_
                            "jsValues": "",_x000D_
                            "jsPlaceholder": false,_x000D_
                            "jsIdAttribute": "",_x000D_
                            "jsClassAttribute": "",_x000D_
                            "jssIcon": "typcn typcn-document-text",_x000D_
                            "noChildren": true_x000D_
                        }]_x000D_
                    }_x000D_
                ]_x000D_
            },_x000D_
            {_x000D_
                "jssType": "paragraph",_x000D_
                "jssSelectLabel": "Paragraph - p",_x000D_
                "jssSelectGroup": "jssItem",_x000D_
                "jsName": "paragraph-993",_x000D_
                "jssContent": "* Required",_x000D_
                "jssIcon": "dashicons dashicons-editor-paragraph",_x000D_
                "noChildren": true_x000D_
            }_x000D_
        ]_x000D_
        _x000D_
    },_x000D_
    {_x000D_
        "jssType": "submit",_x000D_
        "jssSelectLabel": "Submit",_x000D_
        "jssSelectGroup": "jsTag",_x000D_
        "jsName": "submit-704",_x000D_
        "jssLabel": "Send",_x000D_
        "jsValues": "",_x000D_
        "jsRequired": false,_x000D_
        "jsIdAttribute": "",_x000D_
        "jsClassAttribute": "",_x000D_
        "jssIcon": "typcn typcn-mail",_x000D_
        "noChildren": true_x000D_
    },_x000D_
    _x000D_
];_x000D_
_x000D_
_x000D_
_x000D_
_x000D_
 function findObjectByLabel(obj, label) {_x000D_
       for(var elements in obj){_x000D_
           if (elements === label){_x000D_
                console.log(obj[elements]);_x000D_
           }_x000D_
            if(typeof obj[elements] === 'object'){_x000D_
            findObjectByLabel(obj[elements], 'jssType');_x000D_
           }_x000D_
          _x000D_
       }_x000D_
};_x000D_
_x000D_
 findObjectByLabel(treeData, 'jssType');
_x000D_
_x000D_
_x000D_


modify from Peter Olson's answer: https://stackoverflow.com/a/8085118

  1. can avoid string value !obj || (typeof obj === 'string'
  2. can custom your key

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
}

- , ,

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.

_x000D_
_x000D_
(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
}

})();
_x000D_
_x000D_
_x000D_

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.

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
}

_x000D_
_x000D_
(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
}
})();
_x000D_
_x000D_
_x000D_

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:

  1. the code changes the value of input parameters (arguments) [lines 2 & 5],
  2. the code calls Array.prototype.push and Array.prototype.pop on every single item [lines 5 & 8],
  3. the code only does a pointer-comparison for the constructor which does not work on out-of-window objects [line 7],
  4. the code duplicates the array returned from Object.values [line 8],
  5. the code does not localize window.Object or window.Object.values [line 9],
  6. and the code needlessly calls Object.values on arrays [line 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.

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);
}

_x000D_
_x000D_
(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 + '" :(');
}

})();
_x000D_
_x000D_
_x000D_

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.

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.


Here is a solution using object-scan

_x000D_
_x000D_
// 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: [] }
_x000D_
.as-console-wrapper {max-height: 100% !important; top: 0}
_x000D_
<script src="https://bundle.run/[email protected]"></script>
_x000D_
_x000D_
_x000D_

Disclaimer: I'm the author of object-scan


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;
}

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");

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.

Searching element inside collection is more efficient then find element inside tree (recursion, addition dynamic function creation, closure).