102

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: []
    }
  ]
}
Tobias Tengler
  • 6,848
  • 4
  • 20
  • 34
NewToThis
  • 3,197
  • 5
  • 22
  • 10
  • You're wanting to search through all levels of the object for an arbitrary label? (Aha, traverse, that was the word I was looking for.) – Dave Nov 10 '11 at 19:16

15 Answers15

102

2023 Update:

A lot of people have asked about the non-recursive approach, so here it is:

const iterate = (obj) => {
  const stack = [obj];
  while (stack?.length > 0) {
    const currentObj = stack.pop();
    Object.keys(currentObj).forEach(key => {
      console.log(`key: ${key}, value: ${currentObj[key]}`);
      if (typeof currentObj[key] === 'object' && currentObj[key] !== null) {
        stack.push(currentObj[key]);
      }
    });
  }
};

REPL example

Original Answer with Recursion

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' && obj[key] !== null) {
            iterate(obj[key])
        }
    })
}

REPL example

Menelaos Kotsollaris
  • 5,776
  • 9
  • 54
  • 68
  • 2
    If object is "big" in terms of nested items, you'll get a stack overflow error. Better to use iterative techniques. – Alexander Kim Jun 09 '21 at 04:48
  • 1
    I found this to be the clearest, most concise answer after a lot of searching. I agree for "big" objects this might not be ideal. For what it's worth it worked fine for me with about 70 total nested key/value pairs (which I don't consider "big", but everyone is going to have a different interpretation of that). – billoverton Jun 16 '21 at 21:18
  • 1
    Recursion is a bad idea in Javascript and NodeJs (https://node.green/#ES2015-optimisation-proper-tail-calls--tail-call-optimisation-) – Diego Alberto Zapata Häntsch Mar 04 '22 at 21:00
  • If you are dealing with very large objects, you need to convert the recursive function to a loop, [something along these lines](https://replit.com/@mkotsollaris/javascript-iterate-for-loop?v=1). – Menelaos Kotsollaris Jul 19 '22 at 17:44
  • 2
    Clear and concise! Solved my usecase (fortunately not a big stack). Thanks for sharing! – dpigera Oct 09 '22 at 17:27
  • 1
    How can I upvote 2 times? – Ahmad Ismail May 25 '23 at 12:09
70

You can create a recursive function like this to do a depth-first traversal of the cars object.

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");
Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • In the original post, the sub-cars are not properties of the car object, but are contained in a `subs` array. – James Clark Nov 10 '11 at 19:35
  • @JamesClark I know. It should still work, and it is flexible in case he has more than one array property of if he decides to change the name of `subs` to something else. – Peter Olson Nov 10 '11 at 19:37
  • 18
    Recursion is bad for very deep objects. You will get stack overflow. – Frozen Crayon Feb 09 '15 at 11:33
  • 6
    @ArjunU. It's fairly uncommon to have objects that are hundreds of levels deep. – Peter Olson Feb 09 '15 at 18:53
  • 1
    i am just curious to know. what is the use of **obj.hasOwnProperty(i)** . because if it is inside **for (var i in obj)** loop isn't that means the object 100% has that property ? – Vishal Oct 09 '15 at 12:14
  • 4
    @Vishal Without the `obj.hasOwnProperty(i)`, custom prototype properties will be included. For example, if you define `Array.prototype.first = function(a) { return a[0] }`, then `for(var i in [])` will include the prototype property `first`. – Peter Olson Oct 10 '15 at 00:50
  • Now understood Thanks :-) – Vishal Oct 12 '15 at 09:58
  • It took me a long time to figure out the use of *obj.hasOwnProperty(i)*. It would be better to to stay on the answer. – Luis Martins Feb 27 '18 at 18:17
  • 1
    Testing this out returns a call stack error. unless 'Chevrolet' is on the surface. This doesn't seem to work for deeply nested objects. – c0dezer019 Oct 07 '20 at 05:08
  • 3
    I am getting this error `RangeError: Maximum call stack size exceeded` – Prathamesh More Dec 21 '20 at 12:40
  • PTC/STC, anyone? – kawerewagaba Oct 28 '21 at 10:12
13

- , ,

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.

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

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:

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

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

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.

Jack G
  • 4,553
  • 2
  • 41
  • 50
4

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;
}
Peter Olson
  • 139,199
  • 49
  • 202
  • 242
James Clark
  • 1,765
  • 13
  • 17
3

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');
Jasvir
  • 31
  • 2
3

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;
}
ChiralMichael
  • 1,214
  • 9
  • 20
3

Here is a solution using 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>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24
1

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
}
vikyd
  • 1,807
  • 1
  • 19
  • 28
1

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

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);
Mansour
  • 204
  • 4
  • 12
1

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)
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
1

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);            
            };
        }
    }
}
Petr Spacek
  • 629
  • 6
  • 8
1

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

VadimB
  • 5,533
  • 2
  • 34
  • 48
0

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

Dubas
  • 2,855
  • 1
  • 25
  • 37
Shahram
  • 300
  • 1
  • 7
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));
-1
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");
Til
  • 5,150
  • 13
  • 26
  • 34
  • 1
    While this may help OP, it's better to add more details, explanation, examples, etc. – Til May 28 '19 at 03:35
  • In this solution, I just use recursive function call. Firstly check the first object's label, then checks its child's label recursively. – Hein Htet Zaw Oct 06 '20 at 06:17