Home > database >  Filtering an array of nested objects by string value
Filtering an array of nested objects by string value

Time:05-18

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:

  1. If the current level's name includes the search value, include the current level and all children in the subitems or subsubitems arrays as available.
  2. Otherwise, repeat the same check for each item in the subitems or subsubitems 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; }

  • Related