'Filtering an array of nested objects by string value
I'm trying to filter an array with nested objects like this:
const items = [
{
"id": 1,
"name": "test1",
"subitems": [
{
"id": 2,
"name": "test2",
"subsubitems": [
{
"id": 3,
"name": "test3",
},
{
"id": 4,
"name": "test4",
}
]
}
]
},
{
"id": 10,
"name": "test10",
"subitems": [
{
"id": 20,
"name": "test20",
"subsubitems": [
{
"id": 30,
"name": "test30",
}
]
}
]
}
]
const filteredResults = items.filter((item) =>
item.subitems.some((subitem) =>
subitem.subsubitems.some((subsubitem) =>
subsubitem.name.includes('test3')
)
)
)
console.log(filteredResults)
But it's not filtering correctly, the original array is being returned. Right now I'm just attempting to filter at the subsubitems level. Ultimately I want to return an array with any matches to name at any level.
So test2 would return an array like this:
[
{
"id": 1,
"name": "test1",
"subitems": [
{
"id": 2,
"name": "test2",
"subsubitems": [
{
"id": 3,
"name": "test3",
},
{
"id": 4,
"name": "test4",
}
]
}
]
}
]
And test3 would return an array like this:
[
{
"id": 1,
"name": "test1",
"subitems": [
{
"id": 2,
"name": "test2",
"subsubitems": [
{
"id": 3,
"name": "test3",
}
]
}
]
},
{
"id": 10,
"name": "test10",
"subitems": [
{
"id": 20,
"name": "test20",
"subsubitems": [
{
"id": 30,
"name": "test30",
}
]
}
]
}
]
And test would return everything.
Solution 1:[1]
If I'm understanding correctly, the process is:
- If the current level's
nameincludes the search value, include the current level and all children in thesubitemsorsubsubitemsarrays as available. - Otherwise, repeat the same check for each item in the
subitemsorsubsubitemsarrays as available and, if there are any results, include the current item.
I think the following sample gets you this behavior. Notice that when searching for "test2", the items with names "test3" and "test4" are included like in your question. Also, "test20" is included because this is searching at the subitems level in addition to the subsubitems level and "test20" includes "test2".
const items = [
{ id: 1, name: "test1", subitems: [
{ id: 2, name: "test2", subsubitems:[
{ id: 3, name: "test3" },
{ id: 4, name: "test4" }
]}
]},
{ id: 10, name: "test10", subitems: [
{ id: 20, name: "test20", subsubitems: [
{ id: 30, name: "test30"}
]}
]}
];
const deepFilter = (items, value) => items.reduce((arr, cur) => {
if (cur.name.includes(value)) arr.push(cur);
else if (cur.hasOwnProperty('subitems')) {
const subItems = deepFilter(cur.subitems, value);
if (subItems.length) {
cur.subitems = subItems;
arr.push(cur);
}
}
else if (cur.hasOwnProperty('subsubitems')) {
const subSubItems = deepFilter(cur.subsubitems, value);
if (subSubItems.length) {
cur.subsubitems = subSubItems;
arr.push(cur);
}
}
return arr;
}, []);
console.log('test2:');
console.log(deepFilter(items, 'test2'));
console.log('test3:');
console.log(deepFilter(items, 'test3'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Solution 2:[2]
You could take a function for checking the actual level and search for sub levels and if a sublevel has a lenght take the actual level as result.
const
filter = childrenKey => o => {
if (o.name === value) return o;
const childrenValues = (o[childrenKey] || []).flatMap(filter('sub' + childrenKey));
return childrenValues.length ? { ...o, [childrenKey]: childrenValues } : [];
},
data = [{ id: 1, name: "test1", subitems: [{ id: 2, name: "test2", subsubitems: [{ id: 3, name: "test3" }, { id: 4, name: "test4" }] }] }, { id: 10, name: "test10", subitems: [{ id: 20, name: "test20", subsubitems: [{ id: 30, name: "test30" }] }] }],
value = 'test3',
result = data.flatMap(filter('subitems'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
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 | Nina Scholz |
