Search code examples
javascriptfunctionrecursiontreechildren

Filter nested objects by property value recursively and keep array structure


I have the following array of objects with nested elements in the children property. I need to get objects by their id if the id matches.

[
  {
    "id": 10,
    "name": "Scenarios",
    "value": null,
    "children": [
      {
        "id": 12,
        "name": "Scenario status",
        "value": null,
        "children": []
      }
    ]
  },
  {
    "id": 11,
    "name": "Forecast source",
    "value": null,
    "children": []
  },
  {
    "id": 16787217,
    "name": "Item@Cust",
    "value": null,
    "children": [
      {
        "id": 16787230,
        "name": "Customer",
        "value": null,
        "children": [
          {
            "id": 16787265,
            "name": "Site",
            "value": null,
            "children": []
          },
          {
            "id": 16787291,
            "name": "Commercial Network",
            "value": null,
            "children": []
          },
          {
            "id": 16787296,
            "name": "Distribution Site",
            "value": null,
            "children": []
          }
        ]
      },
      {
        "id": 16787245,
        "name": "Item@Site",
        "value": null,
        "children": [
          {
            "id": 16787266,
            "name": "Family@Warehouse",
            "value": null,
            "children": []
          }
        ]
      },
      {
        "id": 16787254,
        "name": "Item",
        "value": null,
        "children": [
          {
            "id": 16787260,
            "name": "Family",
            "value": null,
            "children": [
              {
                "id": 16787264,
                "name": "Product line",
                "value": null,
                "children": []
              }
            ]
          },
          {
            "id": 16787261,
            "name": "Group 1",
            "value": null,
            "children": []
          }
        ]
      }
    ]
  },
  {
    "id": 16787267,
    "name": "Supplier",
    "value": null,
    "children": []
  },
  {
    "id": 16787297,
    "name": "SKU",
    "value": null,
    "children": []
  }
]

If no match on root element is found, the function should lookup its children to see if there is a match, and the first children match should be pushed on the root level.

For exemple, I have a list of ids: [12, 16787217, 16787245, 16787266]

The function should return only objects where ids matches while keeping hierarchy if matching parent id have matching children id, so it should return this:

[
  {
    "id": 12,
    "name": "Scenario status",
    "value": null,
    "children": []
  },
  {
    "id": 16787217,
    "name": "Item@Cust",
    "value": null,
    "children": [
      {
        "id": 16787245,
        "name": "Item@Site",
        "value": null,
        "children": [
          {
            "id": 16787266,
            "name": "Family@Warehouse",
            "value": null,
            "children": []
          }
        ]
      }
    ]
  }
]

As for now, I can get only the first level elements if they are found with this function:

filterArray(array: Array<any>, ids: Array<number>) {
        array = array.filter(el => ids.includes(el.id));
        for (let i = 0; i < array.length; i++) {
            this.filterArray(array[i].children, ids);
        }
        console.log(array);
    }

Anyone have an idea on how to achieve this?


Solution

  • You could reduce the array and take the nodes with found id and children or only the children at the base level.

    const
        find = (r, { children = [], ...o }) => {
            children = children.reduce(find, []);
            if (ids.includes(o.id)) r.push({ ...o, children });
            else if (children.length) r.push(...children);
            return r;
        },
        data = [{ id: 10, name: "Scenarios", value: null, children: [{ id: 12, name: "Scenario status", value: null, children: [] }] }, { id: 11, name: "Forecast source", value: null, children: [] }, { id: 16787217, name: "Item@Cust", value: null, children: [{ id: 16787230, name: "Customer", value: null, children: [{ id: 16787265, name: "Site", value: null, children: [] }, { id: 16787291, name: "Commercial Network", value: null, children: [] }, { id: 16787296, name: "Distribution Site", value: null, children: [] }] }, { id: 16787245, name: "Item@Site", value: null, children: [{ id: 16787266, name: "Family@Warehouse", value: null, children: [] }] }, { id: 16787254, name: "Item", value: null, children: [{ id: 16787260, name: "Family", value: null, children: [{ id: 16787264, name: "Product line", value: null, children: [] }] }, { id: 16787261, name: "Group 1", value: null, children: [] }] }] }, { id: 16787267, name: "Supplier", value: null, children: [] }, { id: 16787297, name: "SKU", value: null, children: [] }],
        ids = [12, 16787217, 16787245, 16787266],
        result = data.reduce(find, []);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }