Search code examples
traversal

Traverse through an Array to obtain nested object


I am trying to traverse through my array to get a specific object nested inside of it.

Some objects contain a children property, which should be traversed until a matching object is found.

Here's some example data, I am trying to obtain the object with id as 4

const items = [{
    id: 1,
    title: 'Title for Item 1'
  },
  {
    id: 2,
    title: 'Title for Item 2',
    children: [
        { 
        id: 3,
        title: "Title for Item 3",
        children: [
            {
            id: 4,
            title: "Title for Item 4",
          }
        ]
      }
    ]
  },
]

I've written some traversal code but it returns undefined.

const items = [{
    id: 1,
    title: 'Title for Item 1'
  },
  {
    id: 2,
    title: 'Title for Item 2',
    children: [
        { 
        id: 3,
        title: "Title for Item 3",
        children: [
            {
            id: 4,
            title: "Title for Item 4",
          }
        ]
      }
    ]
  },
]

const getItem = (items) => {
  if (!items) return;
  const item = items && items.find(i => i.id === 4);
  if (!item) {
    items.forEach(i => {
      return getItem(i.children)
    })
    // This is where undefined is returned
  } else {
    console.log({
      item
    }) // Prints the correct object.
    return item;
  }
};


const s = getItem(items); // undefined

document.querySelector('#foo').textContent = s ? s : 'undefined';
<div id="foo"></div>


Solution

  • At least two issues explain why it does not work:

    1. A return statement in a forEach callback will return the returned value to nowhere. Nothing happens with it.
    2. The result of the recursive call is not checked. It needs to be checked to see if it is defined. Depending on that you can decide whether to continue the loop or exit from it.

    Replace that forEach with a for...of loop so you can return "out of it", but only do that when you have a match, otherwise you need to continue the loop:

    for (const item of items) {
        const match = getItem(item.children);
        if (match) return match;
    }
    

    Note that in your snippet you should not set the textContent to the return value, as that is an object and will get converted to the string "[Object object]". You could for instance just grab the title string and put that in textContent:

    const items = [{
        id: 1,
        title: 'Title for Item 1'
      },
      {
        id: 2,
        title: 'Title for Item 2',
        children: [
            { 
            id: 3,
            title: "Title for Item 3",
            children: [
                {
                id: 4,
                title: "Title for Item 4",
              }
            ]
          }
        ]
      },
    ]
    
    const getItem = (items) => {
      if (!items) return;
      const item = items && items.find(i => i.id === 4);
      if (!item) {
        for (const item of items) {
            const match = getItem(item.children);
            if (match) return match;
        }
      } else {
        console.log({
          item
        }) // Prints the correct object.
        return item;
      }
    };
    
    
    const s = getItem(items); // undefined
    
    document.querySelector('#foo').textContent = s ? s.title : 'undefined';
    <div id="foo"></div>