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.
CodePudding user response:
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; }
CodePudding user response:
If I'm understanding correctly, the process is:
- If the current level's
name
includes the search value, include the current level and all children in thesubitems
orsubsubitems
arrays as available. - Otherwise, repeat the same check for each item in the
subitems
orsubsubitems
arrays 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; }