16

Consider the following deeply nested array:

const array = [
    {
        id: 1,
        name: "bla",
        children: [
            {
                id: 23,
                name: "bla",
                children: [{ id: 88, name: "bla" }, { id: 99, name: "bla" }]
            },
            { id: 43, name: "bla" },
            {
                id: 45,
                name: "bla",
                children: [{ id: 43, name: "bla" }, { id: 46, name: "bla" }]
            }
        ]
    },
    {
        id: 12,
        name: "bla",
        children: [
            {
                id: 232,
                name: "bla",
                children: [{ id: 848, name: "bla" }, { id: 959, name: "bla" }]
            },
            { id: 433, name: "bla" },
            {
                id: 445,
                name: "bla",
                children: [
                    { id: 443, name: "bla" },
                    {
                        id: 456,
                        name: "bla",
                        children: [
                            {
                                id: 97,
                                name: "bla"
                            },
                            {
                                id: 56,
                                name: "bla"
                            }
                        ]
                    }
                ]
            }
        ]
    },
    {
        id: 15,
        name: "bla",
        children: [
            {
                id: 263,
                name: "bla",
                children: [{ id: 868, name: "bla" }, { id: 979, name: "bla" }]
            },
            { id: 483, name: "bla" },
            {
                id: 445,
                name: "bla",
                children: [{ id: 423, name: "bla" }, { id: 436, name: "bla" }]
            }
        ]
    }
];

How would I grab a certain object by key that might be deeply nested, using recursion? I have tried this, but this won't work for nesting deeper than 2 levels, it then just returns undefined:

const findItemNested = (arr, itemId, nestingKey) => {
    for (const i of arr) {
        console.log(i.id);
        if (i.id === itemId) {
            return i;
        }
        if (i[nestingKey]) {
            findItemNested(i[nestingKey], itemId, nestingKey);
        }
    }
};

The result should be:

const res = findItemNested(array, 959, "children"); >> { id: 959, name: "bla" }

This can perhaps also be achieved using .find, or just to flatten the array (by the children key), but using recursion seems like the most logical solution to me. Does anybody have a solution to this?

Thanks in advance :).

Fabian Tjoe A On
  • 1,383
  • 5
  • 18
  • 41

7 Answers7

36

You might use a recursive reduce:

const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];

const findItemNested = (arr, itemId, nestingKey) => (
  arr.reduce((a, item) => {
    if (a) return a;
    if (item.id === itemId) return item;
    if (item[nestingKey]) return findItemNested(item[nestingKey], itemId, nestingKey)
  }, null)
);
const res = findItemNested(array, 959, "children");
console.log(res);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I don't understand when "a" var in reduce function is affected ?! – Goku Mar 11 '20 at 14:01
  • 2
    The `a` is the accumulator, the value returned from the last iteration. If truthy, a match has been found.on a past iteration, and gets returned to the next iteration. If falsy, a match hasn't been found yet, so the `reduce` either returns the current item if it's a match: `return item` or it searches for a recursive match with `return findItemNested(...`. – CertainPerformance Mar 11 '20 at 21:00
  • Thanks a lot .. same i was looking for . – Mahi Sep 22 '21 at 05:38
7

This should work:

function findByIdRecursive(array, id) {
  for (let index = 0; index < array.length; index++) {
    const element = array[index];
    if (element.id === id) {
      return element;
    } else {
      if (element.children) {
        const found = findByIdRecursive(element.children, id);

        if (found) {
          return found;
        }
      }
    }
  }
}
Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47
5

You might also use recursion with Array.find like below

const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];


function findById(arr, id, nestingKey) {
  
  // if empty array then return
  if(arr.length == 0) return
  
  // return element if found else collect all children(or other nestedKey) array and run this function
  return arr.find(d => d.id == id) 
      || findById(arr.flatMap(d => d[nestingKey] || []), id) 
      || 'Not found'
}

console.log(findById(array, 12, 'children'))

console.log(findById(array, 483, 'children'))

console.log(findById(array, 1200, 'children'))
Nitish Narang
  • 4,124
  • 2
  • 15
  • 22
3

We use object-scan for most of our data processing. It's awesome for all sorts of things, but does take a while to wrap your head around. This is how one could answer your question:

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

const find = (data, id) => objectScan(['**(^children$).id'], {
  abort: true,
  rtn: 'parent',
  useArraySelector: false,
  filterFn: ({ value }) => value === id
})(data);

const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];

console.log(find(array, 12));
// => { id: 12, name: 'bla', children: [ { id: 232, name: 'bla', children: [ { id: 848, name: 'bla' }, { id: 959, name: 'bla' } ] }, { id: 433, name: 'bla' }, { id: 445, name: 'bla', children: [ { id: 443, name: 'bla' }, { id: 456, name: 'bla', children: [ { id: 97, name: 'bla' }, { id: 56, name: 'bla' } ] } ] } ] }
console.log(find(array, 483));
// => { id: 483, name: 'bla' }
console.log(find(array, 959));
// => { id: 959, name: 'bla' }
console.log(find(array, 1200));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.7.1"></script>

Disclaimer: I'm the author of object-scan

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

You can do:

const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];
const findItemNested = (arr, itemId, nestingKey) => arr.reduce((a, c) => {
  return a.length
    ? a
    : c.id === itemId
      ? a.concat(c)
      : c[nestingKey]
        ? a.concat(findItemNested(c[nestingKey], itemId, nestingKey))
        : a
}, []);
const res = findItemNested(array, 959, "children");

if (res.length) {
  console.log(res[0]);
}
Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
0

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:

New browsers will have Array.prototype.flatten but in this case I've added the flatten function separately.

const array = [{"id":1,"name":"bla","children":[{"id":23,"name":"bla","children":[{"id":88,"name":"bla"},{"id":99,"name":"bla"}]},{"id":43,"name":"bla"},{"id":45,"name":"bla","children":[{"id":43,"name":"bla"},{"id":46,"name":"bla"}]}]},{"id":12,"name":"bla","children":[{"id":232,"name":"bla","children":[{"id":848,"name":"bla"},{"id":959,"name":"bla"}]},{"id":433,"name":"bla"},{"id":445,"name":"bla","children":[{"id":443,"name":"bla"},{"id":456,"name":"bla","children":[{"id":97,"name":"bla"},{"id":56,"name":"bla"}]}]}]},{"id":15,"name":"bla","children":[{"id":263,"name":"bla","children":[{"id":868,"name":"bla"},{"id":979,"name":"bla"}]},{"id":483,"name":"bla"},{"id":445,"name":"bla","children":[{"id":423,"name":"bla"},{"id":436,"name":"bla"}]}]}];


const flatten = (arr) =>
  arr.reduce((result, item) => result.concat(item), []);
const findBy = (findFunction, subItemsKey) => (array) =>
  //array is empty (can be when children of children of children does not exist)
  array.length === 0
    ? undefined //return undefined when array is empty
    : array.find(findFunction) || //return item if found
      findBy(findFunction, subItemsKey)(//call itself when item is not found
        flatten(
          //take children from each item and flatten it
          //([[child],[child,child]])=>[child,child,child]
          array.map((item) => item[subItemsKey] || []),
        ),
      );
const findChildrenById = (array) => (value) =>
  findBy((item) => item.id === value, 'children')(array);
const findInArray = findChildrenById(array);

console.log('found', findInArray(99));
console.log('not found', findInArray({}));
HMR
  • 37,593
  • 24
  • 91
  • 160
-1

You need to iterate through your objects and then need to be parse each object using recursion. Try the answer mentioned here: JavaScript recursive search in JSON object

code:

`function findNode(id, currentNode) { var i, currentChild, result;

if (id == currentNode.id) {
    return currentNode;
} else {

    // Use a for loop instead of forEach to avoid nested functions
    // Otherwise "return" will not work properly
    for (i = 0; i < currentNode.children.length; i += 1) {
        currentChild = currentNode.children[i];

        // Search in the current child
        result = findNode(id, currentChild);

        // Return the result if the node has been found
        if (result !== false) {
            return result;
        }
    }

    // The node has not been found and we have no more options
    return false;
}

}`

partizanos
  • 1,064
  • 12
  • 22