90

Is there a way (in jQuery or JavaScript) to loop through each object and it's children and grandchildren and so on?

If so... can I also read their name?

Example:

foo :{
  bar:'',
  child:{
    grand:{
      greatgrand: {
        //and so on
      }
    }
  }
}

so the loop should do something like this...

loop start
   if(nameof == 'child'){
     //do something
   }
   if(nameof == 'bar'){
     //do something
   }
   if(nameof =='grand'){
     //do something
   }
loop end
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
Val
  • 17,336
  • 23
  • 95
  • 144

5 Answers5

119

You're looking for the for...in loop:

for (var key in foo)
{
    if (key == "child")
        // do something...
} 

Be aware that for...in loops will iterate over any enumerable properties, including those that are added to the prototype of an object. To avoid acting on these properties, you can use the hasOwnProperty method to check to see if the property belongs only to that object:

for (var key in foo)
{
    if (!foo.hasOwnProperty(key))
        continue;       // skip this property
    if (key == "child")
        // do something...
}

Performing the loop recursively can be as simple as writing a recursive function:

// This function handles arrays and objects
function eachRecursive(obj)
{
    for (var k in obj)
    {
        if (typeof obj[k] == "object" && obj[k] !== null)
            eachRecursive(obj[k]);
        else
            // do something... 
    }
}
jasonmerino
  • 3,220
  • 1
  • 21
  • 38
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 2
    @Val: it would be difficult to crash the browser with object recursion. The object would need to contain a reference to itself as one of the properties :-) – Andy E Apr 02 '10 at 15:53
  • 2
    try: `(a = {})._ = a` – AnnanFay Jul 01 '14 at 22:50
  • 4
    @Annan: yes, that is more or less what I was getting at. The point was simply not to do it ;-) – Andy E Jul 02 '14 at 09:29
  • Are you guys saying this wouldn't work on an object with a DOM reference/circular reference? –  Aug 25 '17 at 19:28
  • 2
    I found I needed to ```// do something``` prior to calling eachRecursive() for Date objects otherwise this implementation skipped over them. Dates are objects so eachRecursive() is called but in that case you don't really want to recurse. That is what I found when using this. – PatS Nov 16 '18 at 19:08
  • 2
    object-keys and for-loop - differences details [here](https://stackoverflow.com/a/54553880/860099) – Kamil Kiełczewski Feb 09 '19 at 13:24
  • For the `eachRecursive` you might want to not do the `else` comparison. Just go into `// do something` always, and there `if (k === "child")` – Arne Jenssen Aug 05 '21 at 04:30
22

You can have an Object loop recursive function with a property execute function propExec built within it.

function loopThroughObjRecurs (obj, propExec) {
  for (var k in obj) {
    if (typeof obj[k] === 'object' && obj[k] !== null) {
      loopThroughObjRecurs(obj[k], propExec)
    } else if (obj.hasOwnProperty(k)) {
      propExec(k, obj[k])
    }
  }
}

Test here:

// I use the foo object of the OP
var foo = {
  bar:'a',
  child:{
    b: 'b',
    grand:{
      greatgrand: {
        c:'c'
      }
    }
  }
}

function loopThroughObjRecurs (obj, propExec) {
  for (var k in obj) {
    if (typeof obj[k] === 'object' && obj[k] !== null) {
      loopThroughObjRecurs(obj[k], propExec)
    } else if (obj.hasOwnProperty(k)) {
      propExec(k, obj[k])
    }
  }
}

// then apply to each property the task you want, in this case just console
loopThroughObjRecurs(foo, function(k, prop) {
  console.log(k + ': ' + prop)
})
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
6

If you want to get back a tree of relationships you can use Object.keys recursively.

function paths(item) {
  function iter(r, p) {
    var keys = Object.keys(r);
    if (keys.length) {
      return keys.forEach(x => iter(r[x], p.concat(x)));
    }
    result.push(p);
  }
  var result = [];
  iter(item, []);
  return result;
}

var data = {
  foo: {
    bar: '',
    child: {
      grand: {
        greatgrand: {}
      }
    }
  }
};

console.log(paths(data));

This can be extended to search for values within an object structure that match a function:

function objectSearch(rootItem, matcher) {
  const visited = [];
  const paths = [];
  function iterate(item, path) {
    if (visited.includes(item)) {
      return;
    }
    visited.push(item);
    if (typeof item === "object" && item !== null) {
      var keys = Object.keys(item);
      if (keys.length) {
        return keys.forEach(key => iterate(item[key], path.concat(key)));
      }
    }
    if (matcher(item)) {
      paths.push(path);
    }
  }
  iterate(rootItem, []);
  return paths;
}
function searchForNaNs(rootItem) {
  return objectSearch(rootItem, (v) => Object.is(NaN, v));
}
var banana = {
  foo: {
    bar: "",
    child: {
      grand: {
        greatgrand: {},
        nanan: "NaN",
        nan: NaN,
      },
    },
  },
};
console.log("There's a NaN at", searchForNaNs(banana)[0].join("."), "in this object:", banana);
1j01
  • 3,714
  • 2
  • 29
  • 30
Rick
  • 1,035
  • 10
  • 18
3

Consider using object-scan. It's powerful for data processing once you wrap your head around it.

One great thing is that the items are traversed in "delete safe" order. So if you delete one, it won't mess up the loop. And you have access to lots of other properties like parents etc.

// const objectScan = require('object-scan');

const obj = { foo: { bar: '', child: { grand: { greatgrand: { /* and so on */ } } } } };

objectScan(['**'], {
  filterFn: ({ property }) => {
    console.log(property);
  }
})(obj);
// => greatgrand
// => grand
// => child
// => bar
// => foo
.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
0

I would recommend using sindresorhus's map-obj & filter-obj utilities ...

Ali Sharif
  • 41
  • 1
  • 5
  • 4
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31546658) – Bhagyesh Apr 20 '22 at 06:16