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.


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]

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

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