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

asked Nov 20, 2018 at 9:59
2
  • 1
    You need to return the inner findItemNested call, otherwise its return value is discarded. Commented Nov 20, 2018 at 10:03
  • Yep, that's one thing I've been doing wrong, thanks! Commented Nov 20, 2018 at 10:06

7 Answers 7

38

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

answered Nov 20, 2018 at 10:05
Sign up to request clarification or add additional context in comments.

2 Comments

I don't understand when "a" var in reduce function is affected ?!
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(....
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;
 }
 }
 }
 }
}
answered Nov 20, 2018 at 10:12

1 Comment

Thanks for the answer! this is one simple way of doing it.
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'))

answered Nov 20, 2018 at 11:04

Comments

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/[email protected]"></script>

Disclaimer: I'm the author of object-scan

answered Nov 5, 2020 at 4:51

1 Comment

object-scan is great! i didnt know it before, thanks!
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]);
}

answered Nov 20, 2018 at 10:22

Comments

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

answered Nov 20, 2018 at 11:37

Comments

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

}`

answered Nov 20, 2018 at 10:04

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.