'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 |