You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:

function findNode (id, array) {
  for (const node of array) {
    if (node.id === id) return node;
    if (node.children) {
      const child = findNode(id, node.children);
      if (child) return child;
    }
  }
}

It will return undefined if there is no match.

Answer from Patrick Roberts on Stack Overflow
Top answer
1 of 4
25

You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:

function findNode (id, array) {
  for (const node of array) {
    if (node.id === id) return node;
    if (node.children) {
      const child = findNode(id, node.children);
      if (child) return child;
    }
  }
}

It will return undefined if there is no match.

2 of 4
3

To avoid the need for manual iteration, you might consider using an array method like reduce instead - return the accumulator if it's truthy (that is, an object was found already), or return the object being iterated over if the ID matches, or recursively iterate over the object's children to find a match.

const data=[{id:'RAKUFNUBNY00UBZ40950',name:'Grade 1 Cover',activityId:'RAKUFNUBNY00UBZ40950',nodeType:'activity',suppressed:!1,hidden:!1},{children:[{id:'SLWDYEQHTZAFA3ALH195',name:'Build Background Video',activityId:'SLWDYEQHTZAFA3ALH195',nodeType:'activity',suppressed:!1,hidden:!1,assetReference:{referenceId:'UWFHA5A1E0EGKCM0W899',assetType:'image'}},{children:[{id:'HQUCD2SSRKMYC2PJM636',name:'Eat or Be Eaten Splash Card',activityId:'HQUCD2SSRKMYC2PJM636',nodeType:'activity',suppressed:!1,hidden:!0},{children:[{id:'ZDTWEZFL13L8516VY480',name:'Interactive Work Text: Eat or Be Eaten',activityId:'ZDTWEZFL13L8516VY480',nodeType:'activity',suppressed:!1,hidden:!0,defaultLaunchMode:'modal'}],}],}],}]

function findId(id, arr) {
  return arr.reduce((a, item) => {
    if (a) return a;
    if (item.id === id) return item;
    if (item.children) return findId(id, item.children);
  }, null);
}
console.log(findId('HQUCD2SSRKMYC2PJM636', data));

๐ŸŒ
Quora
quora.com โ€บ How-do-I-add-items-to-an-array-in-JavaScript-recursively-and-return-that-array-1
How to add items to an array in JavaScript recursively and return that array - Quora
In this example I am using simple values in the array however you can iterate with array of objects, this way you will be able to do manipulations on these object. You can also explore .reduce Array.prototype.reduce() function which is really handy for even more complex data manipulations. ... You will use unshift method for inserting array element first in Javascript.
Discussions

javascript - Recursive iteration over dynamically nested object array - Stack Overflow
I am using angular JS and one of their examples:http://jsfiddle.net/furf/EJGHX/ I need to take the data when the update function occurs and add some values to it before I send to the server. (If d... More on stackoverflow.com
๐ŸŒ stackoverflow.com
May 29, 2016
Javascript recursive array flattening - Stack Overflow
The other answers already did point to the source of the OP's code malfunction. Writing more descriptive code, the problem literally boils down to an "array-detection/-reduce/-concat-recursion" ... (function (Array, Object) { //"use strict"; var array_prototype = Array.prototype, ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
Javascript Recursive functions for array of objects to update or delete from array based on id key - Stack Overflow
I'm trying to create two (2) recursive functions to "loop" over an array of objects like below. I think the two functions are "similar" but they do two different things. Functi... More on stackoverflow.com
๐ŸŒ stackoverflow.com
Return an array of objects from a recursive function in Javascript - Stack Overflow
I'm working on recursive functions. I must push all objects that have the key "data: true" in an array. The console.log in the middle of my function gives me all those objects in separate arrays. ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
๐ŸŒ
freeCodeCamp
freecodecamp.org โ€บ news โ€บ flatten-array-recursion
How to Flatten an Array in JavaScript Using Recursion
August 18, 2022 - If you carefully observe the above code, line 6 print1ToNo(currentValue + 1) is calling the same function with a new value (whatever the currentValue was, plus 1, i.e currentValue + 1). And it keeps doing it, until the currentValue goes past N, because that's when we told it to return. Now, this is what recursion means. Now, let's get back to our main problem โ€“ we need to flatten an Array. Assume that we have just one level of nesting (of course, we can have multiple nestings, but for now we'll deal with one).
๐ŸŒ
TutorialsPoint
tutorialspoint.com โ€บ article โ€บ recursively-loop-through-an-array-and-return-number-of-items-with-javascript
Recursively loop through an array and return number of items with JavaScript?
const countInNestedArray = (arr, query) => { let count = 0; for (let item of arr) { if (Array.isArray(item)) { count += countInNestedArray(item, query); } else if (item === query) { count++; } } return count; }; const names = ["rakesh", ["kalicharan", "krishna", "rakesh", "james", ["michael", "nathan", "rakesh", "george"]]]; console.log(countInNestedArray(names, "rakesh")); console.log(countInNestedArray(names, "krishna")); ... Recursive array traversal is perfect for nested structures of unknown depth.
๐ŸŒ
ITNEXT
itnext.io โ€บ explode-an-array-into-a-deeply-nested-object-with-this-simple-recursive-function-4094ac1eeb8b
Explode an Array into a deeply nested Object with this simple, recursive function.
April 28, 2021 - Convert arrays into deeply nested index objects using a simple recursive 'explode' function that leverages Javascript's reducers, computed properties, and object spread notation.
๐ŸŒ
GitHub
github.com โ€บ zeke โ€บ objectify-array
GitHub - zeke/objectify-array: Recursively convert arrays of objects into a single keyed object tree
var todos = [ { id: 10, description: 'Learn Things', tags: [ { name: 'a', score: 15 }, { name: 'b', score: 83 } ] }, { id: 20, description: 'Do Things', tags: [ { name: 'x', score: 3 }, { name: 'y', score: 9 } ] }, ] var todosMap = objectifyArray(todos, { by: ['id', 'name'], recursive: true }) todosMap[10] //=> { id: 10, description: ..., tags: ... } todosMap[20] //=> { id: 10, description: ..., tags: ... } todosMap[10].tags.a.score //=> 15 todosMap[10].tags.b.score //=> 83 ยท you can specify options.by to be a function, this function will receive the element and should return the key to be used for this element. var users = [ { id: 'u', name: 'Alice', hash: 22 } ] var peopleMap = objectifyArray(people, { by: (x) => `${x.id}_${x.hash}` }) peopleMap.u_22.name //=> Alice
Starred by 7 users
Forked by 7 users
Languages ย  JavaScript 100.0% | JavaScript 100.0%
Find elsewhere
๐ŸŒ
Medium
medium.com โ€บ hackernoon โ€บ you-might-not-need-that-recursive-function-in-javascript-275651522185
You Might Not Need that Recursive Function in JavaScript | by Nick Scialli | HackerNoon.com | Medium
February 29, 2020 - Then, we recursively call that ... work, there is a better way! We can simply iterate through the array and assign each object to the children array of its parent object....
Top answer
1 of 1
3

generics

Let's start with a immutable update(t, func) that takes a value of any type, t, and a callable updater function, func. func transforms t per the caller's specified return value. If no value is returned, ie undefined, then update will remove that value from the tree -

function update(t, func) {
  switch (t?.constructor) {
    case Object:
      return Object.entries(t).reduce((r, [k, v]) => {
        const newValue = update(func(v), func)
        if (newValue !== undefined) r[k] = newValue
        return r
      }, {})
    case Array:
      return t.flatMap(v => {
        const newValue = update(func(v), func)
        return newValue === undefined ? [] : [newValue]
      })
    default:
      return t
  }
}

Immutable remove(t, func) can be defined as a specialization of update -

function remove(t, func) {
  return update(t, v => Boolean(func(v)) ? undefined : v)
}

special forms

The functions can be further specialized to match your particular needs. updateWithObj(t, obj) will recursively update t where a node's id matches obj.id -

function updateWithObj(t, obj) {
  return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}

Likewise removeWithObj(t, obj) recursively removes from t where a node's id matches obj.id -

function removeWithObj(t, obj) {
  return remove(t, v => v.id == obj.id)
}

examples

Let's create some sample data. For what it's worth, update doesn't care whether it is an array of elements, [...] or a single object, {...} -

const data = [
  {id: 1, data: 50 },
  {id: 2, data: {id: 3, data: "foo"}},
  {id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]

We'll start with a simple update on obj.id == 1. Note the existing data attribute remains in tact and a new ok attribute is added. All other nodes remain unchanged -

console.log(updateWithObj(data, {id: 1, ok: ""}))
[
  {
    "id": 1,
    "data": 50,  //  remains unchanged
    "ok": ""  //  updated
  },
  {
    "id": 2,
    "data": {
      "id": 3,
      "data": "foo"
    }
  },
  {
    "id": 4,
    "data": [
      {
        "id": 5,
        "data": 3.141
      },
      {
        "id": 6,
        "data": {
          "id": 7,
          "data": "bar"
        }
      }
    ]
  }
]

Here we see a deeply nested update with obj.id == 7. Note the data attribute for this node is updated and a new ok attribute is added -

console.log(updateWithObj(data, {id: 7, data: 0.123, ok: ""}))
[
  {
    "id": 1,
    "data": 50
  },
  {
    "id": 2,
    "data": {
      "id": 3,
      "data": "foo"
    }
  },
  {
    "id": 4,
    "data": [
      {
        "id": 5,
        "data": 3.141
      },
      {
        "id": 6,
        "data": {
          "id": 7,
          "data": 0.123, //  updated
          "ok": ""     //  updated
        }
      }
    ]
  }
]

Now let's see removal using removeWithObj. Notice obj.id == 6 is removed along with its descendants -

console.log(removeWithObj(data, {id: 6}))
[
  {
    "id": 1,
    "data": 50
  },
  {
    "id": 2,
    "data": {
      "id": 3,
      "data": "foo"
    }
  },
  {
    "id": 4,
    "data": [
      {
        "id": 5,
        "data": 3.141
      }
      //  node removed
    ]
  }
]

live demo

Here's a demo you can run in your own browser -

function update(t, func) {
  switch (t?.constructor) {
    case Object:
      return Object.entries(t).reduce((r, [k, v]) => {
        const newValue = update(func(v), func)
        if (newValue !== undefined) r[k] = newValue
        return r
      }, {})
    case Array:
      return t.flatMap(v => {
        const newValue = update(func(v), func)
        return newValue === undefined ? [] : [newValue]
      })
    default:
      return t
  }
}

function remove(t, func) {
  return update(t, v => Boolean(func(v)) ? undefined : v)
}

function updateWithObj(t, obj) {
  return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}

function removeWithObj(t, obj) {
  return remove(t, v => v.id == obj.id)
}

const data = [
  {id: 1, data: 50 },
  {id: 2, data: {id: 3, data: "foo"}},
  {id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]

console.log(updateWithObj(data, {id: 1, ok: ""}))
console.log(updateWithObj(data, {id: 7, data: 0.123, ok: ""}))
console.log(removeWithObj(data, {id: 6}))
.as-console-wrapper { min-height: 100%; top: 0; }

why undefined?

@Scott's comment draws attention to use of undefined as the mechanism for removal. I have always advocated for the programmer to reserve the right to use undefined for her/his particular needs. If a user gives undefined to a program, they can expect undefined behavior. For update, the undefined value is explicitly used to make a value not defined, or not there, ie remove it.

Other reasons support this choice. In most cases, an object with an explicitly undefined key behaves the same as one without the key. If the user really wants a var/key to be present but not yet set to a value, this is the perfect use of null -

const a = { foo: undefined }  // foo is defined, but also not defined ??
const b = {}                  // does not have foo
console.log(a.foo, b.foo)     // same behavior
// undefined undefined

JSON considers an undefined as "not defined" and so removes it when serializing an object -

const o = { a: undefined, b: null, c: false, d: 0, e: "" }
const j = JSON.stringify(o)

// "a" is not defined, so it's not serialized
console.log(j)

// looking for "a"? it's not defined :D
console.log(JSON.parse(j).a)

// undefined input gives undefined output. it's not defined :D
console.log(JSON.stringify(undefined))

ReScript plainly encodes None (ie "no value") as undefined in its Option module. Any Some(value) is represented as value -

// rescript
let foo = Some(1)

switch foo {
  | Some(z) => Js.log(z)
  | None => Js.log("no value")
}
// compiled javascript
var foo = 1;

if (foo !== undefined) {
  console.log(foo);
} else {
  console.log("no value");
}

explicit symbol

Maybe none of that convinces you and your fragile program still depends on having that undefined appear in the output. An explicit none sentinel can be used to signal to update that a particular value should be removed -

const none = Symbol() //  removal sentinel
function update(t, func) {
  switch (t?.constructor) {
    case Object:
      return Object.entries(t).reduce((r, [k, v]) => {
        const newValue = update(func(v), func)
        if (newValue !== none) r[k] = newValue // 
        return r
      }, {})
    case Array:
      return t.flatMap(v => {
        const newValue = update(func(v), func)
        return newValue === none ? [] : [newValue] // 
      })
    default:
      return t
  }
}
function remove(t, func) {
  return update(t, v => Boolean(func(v)) ? none : v) // 
}
๐ŸŒ
GoLinuxCloud
golinuxcloud.com โ€บ home โ€บ programming โ€บ recursive search in array of objects javascript? [solved]
Recursive search in array of objects JavaScript? [SOLVED] | GoLinuxCloud
January 17, 2023 - This function will check all elements ... recursive search in an array of objects by defining a function that takes in the array, the current index, and the target item as its parameters....
๐ŸŒ
CheatCode
cheatcode.co โ€บ blog โ€บ how-to-recursively-traverse-an-object-with-javascript
How to Recursively Traverse an Object with JavaScript | CheatCode
In this tutorial, we learned how to recursively traverse an object using JavaScript. We learned how to create a base function that was able to loop over the keys of an object we passed it, looking for a matching key and value pair.
๐ŸŒ
OpenReplay
blog.openreplay.com โ€บ explaining-recursion-in-javascript
Explaining Recursion in JavaScript
October 13, 2022 - Letโ€™s see how to create a deep copy of an object using recursion. const createDeepCopy = (input) => { if (typeof input !== "object" || input === null) { return input; //BASE CASE } let copy = Array.isArray(input) ?
๐ŸŒ
DEV Community
dev.to โ€บ askyt โ€บ recursively-mapping-objects-in-javascript-5gne
Recursively Mapping Objects in JavaScript - DEV Community
January 30, 2025 - This approach involves defining a recursive function that iterates through each key-value pair of the object, checking if the value is another object. If it is, the function recursively calls itself to map the nested object. function mapObject(obj, ...
๐ŸŒ
Medium
medium.com โ€บ dailyjs โ€บ functional-js-with-es6-recursive-patterns-b7d0813ef9e3
Functional JS with ES6 โ€” Recursive Patterns | by Casey Morris | DailyJS | Medium
October 21, 2018 - The above steps are recursively applied to each sub-array until there are no arrays left, which is flatten to return a sorted array. This can also be implemented using partition, but requires variable assignment. ... Many of the functions above can be converted into reductions, which should increase performance in most, if not all cases. This also shows the flexibility of the reduce function. ... I hope this article helps shed insight on some of the patterns made available with JavaScript and ES6.
Top answer
1 of 16
122

I made a FIDDLE for you. I am storing a stack string and then output it, if the property is of primitive type:

function iterate(obj, stack) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                    iterate(obj[property], stack + '.' + property);
                } else {
                    console.log(property + "   " + obj[property]);
                    $('#output').append($("<div/>").text(stack + '.' + property))
                }
            }
        }
    }

iterate(object, '')

Update: 17/01/2019

There used to be a different implementation, but it didn't work. See this answer for a prettier solution

2 of 16
86

The solution from Artyom Neustroev does not work on complex objects, so here is a working solution based on his idea:

function propertiesToArray(obj) {
  const isObject = val =>
    val && typeof val === 'object' && !Array.isArray(val);

  const addDelimiter = (a, b) =>
    a ? `${a}.${b}` : b;

  const paths = (obj = {}, head = '') => {
    return Object.entries(obj)
      .reduce((product, [key, value]) => 
        {
          let fullPath = addDelimiter(head, key)
          return isObject(value) ?
            product.concat(paths(value, fullPath))
          : product.concat(fullPath)
        }, []);
  }

  return paths(obj);
}
  
const foo = {foo: {bar: {baz: undefined}, fub: 'goz', bag: {zar: {zaz: null}, raz: 3}}}
const result = propertiesToArray(foo)
console.log(result)

EDIT (2023/05/23):

4 different (complete) solutions with full descriptions are available on LeetCode: https://leetcode.com/problems/array-of-objects-to-matrix/editorial/?utm_campaign=PostD19&utm_medium=Post&utm_source=Post&gio_link_id=EoZk0Zy9

๐ŸŒ
Medium
medium.com โ€บ @natelapinski โ€บ recursive-array-methods-better-javascript-through-haskell-62c47b02d08c
Recursive Array Methods. Better Javascript through Haskell. | by Nate Lapinski | Medium
December 30, 2019 - In this short article, weโ€™re going to implement some of the most useful methods on Array.prototype using recursion. The goal is to give you some insight into how map, filter, and reduce work, and to demonstrate how to the think about array operations in Javascript in terms of recursion, similar to how Haskell and other pure languages operate on lists.