'ES6 - Finding data in nested arrays

In ES6 using find or filter I'm quite comfortable iterating through to find an element in an array using a value.

However, I'm trying to get a value from a parent array based upon a value from a nested array.

For example, in this data structure:

products: [
  {
    id: 01,
    items: [
      {
        id: 01,
        name: 'apple'
      },
      {
        id: 02,
        name: 'banana'
      },
      {
        id: 03,
        name: 'orange'
      }
    ]
  },
  {
    id: 02,
    items: [
      {
        id: 01,
        name: 'carrot'
      },
      {
        id: 02,
        name: 'lettuce'
      },
      {
        id: 03,
        name: 'peas'
      }
    ]
  },
  {
    id: 03,
    items: [
      {
        id: 01,
        name: 'eggs'
      },
      {
        id: 02,
        name: 'bread'
      },
      {
        id: 03,
        name: 'milk'
      }
    ]
  }
]

If I know the name or id of the object milk, is there a way to find out the id of the element it's nested within?

Currently I have this:

products.find((product) => {
  product.find((prod) => {
    return prod.name === 'milk';
  });
});

Which only returns the object containing milk.



Solution 1:[1]

You have to return something from the callback of the outer find. In fact, for the inner iteration you shouldn't use find but rather some that returns a boolean for whether an element matching the condition exists within the arrray:

products.find((product) => {
  return product.items.some((item) => {
//^^^^^^
    return item.name === 'milk';
  });
});

or in short:

products.find(product => product.items.some(item => item.name === 'milk'));

Then check whether find found something (not null!) and get its .id, the result should be 03. Alternatively, you can filter for the products containing milk as an item and then map all the results to their id:

products.filter(product =>
  product.items.some(item => item.name === 'milk');
).map(product =>
  product.id
) // [03]

Solution 2:[2]

UPDATE

As Ricardo Marimon commented, reduce does not break so it keeps searching over the array, So with that in mind as I don't like to use for loops imperative way of programming, its possible to break early from a reduce by mutating the used array, but that would also be bad, so instead its possible to make a copy and mutate the copy instead too.

// slice creates a copy of products
return products.slice(0).reduce((prev, product, i, arr) => {
    console.log(i);
    const findItem = prev || product.items.find(item => item.name === 'milk');
    if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
    return findItem;
}, undefined);

const products = [
  {id: 1, items: [
    {id: 1, name: 'apple'},
    {id: 2, name: 'banana'},
    {id: 3, name: 'orange'}
  ]},
  {id: 2, items: [
    {id: 1, name: 'carrot'},
    {id: 2, name: 'lettuce'},
    {id: 3, name: 'milk'}
  ]},
  {id: 3, items: [
    {id: 1, name: 'eggs'},
    {id: 2, name: 'bread'},
    {id: 3, name: 'peas'}
  ]}
];

const findItem = products.slice(0).reduce((prev, product, i, arr) => {
    console.log(i);
    const findItem = prev || product.items.find(item => item.name === 'milk');
    if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
    return findItem;
}, undefined);

console.log(findItem);

OLD

The accepted answer didn't do it for me because I wanted the result of the inner find, using both it always gave me the result of the outer filter/find, and I had to use the resulting array to find the value again.

So instead I used reduce with short-circuit to get the inner result.

// undefined as the initial value is necessary, otherwise it gets the first value of the array instead.

return products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined);

const products = [
  {id: 1, items: [
    {id: 1, name: 'apple'},
    {id: 2, name: 'banana'},
    {id: 3, name: 'orange'}
  ]},
  {id: 2, items: [
    {id: 1, name: 'carrot'},
    {id: 2, name: 'lettuce'},
    {id: 3, name: 'peas'}
  ]},
  {id: 3, items: [
    {id: 1, name: 'eggs'},
    {id: 2, name: 'bread'},
    {id: 3, name: 'milk'}
  ]}
];

console.log(products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined));

Solution 3:[3]

I know you mention ES6, but in this case (and if you want to return the inner object) I believe it's better using for/of instead of map/reduce/find:

for (let p of products) {
  for (let i of p.items) {
    if (i.name === 'milk') return i;
  }
}

Solution 4:[4]

To get the item directly without doubling back to get the name/object:

let found;
for ( const category of products ){
    found = category.items.find( item => item.name == "milk" )
    if ( found ) break
}
found // == { id: 3, name: 'milk' }

Solution 5:[5]

Another smart approach:

products
  .map((category) => category.items)
  .flat()
  .find((product) => product.name === 'milk');

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 ariel
Solution 4
Solution 5 Seph