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 - How to find a object in a nested array using recursion in JS - Stack Overflow
This will use recursive find by level, it'll try to find the item in array and then call itself with the children of each item in the array: More on stackoverflow.com
๐ŸŒ stackoverflow.com
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
๐ŸŒ
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%
๐ŸŒ
Ashish Maurya's Blog
blog.theashishmaurya.me โ€บ flatting-an-object-and-array-using-recursion-and-other-methods
Flatting An object and Array Using Recursion and other Methods
September 27, 2022 - Use recursion to flat a nested object by looping over each property and checking if the typeof the property is of type object, then recursively loop over that object and keep adding the result to the result object.
Find elsewhere
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) // 
}
๐ŸŒ
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....
๐ŸŒ
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.
๐ŸŒ
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....
๐ŸŒ
JavaScript.info
javascript.info โ€บ tutorial โ€บ the javascript language โ€บ advanced working with functions
Recursion and stack
The code is short and easy to understand (hopefully?). Thatโ€™s the power of recursion. It also works for any level of subdepartment nesting. ... We can easily see the principle: for an object {...} subcalls are made, while arrays [...] are the โ€œleavesโ€ of the recursion tree, they give immediate result.
๐ŸŒ
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, ...