how to update array of objects if it matches while recursively search in JavaScript

Viewed 541

I would like to update the object with values after a recursive search finds a particular node.

Where do I need to add logic to achieve this?

I would like to get the first found object from the nested array of objects and update the data with selected:true based on the iterations to get the value showTree: true.

Function:

let findDeep = function(data, label) {
  return data.filter(function(e) {
    if (e.label.includes(label)) {
      data.map(el=> el.selected= "true"); // logic to select the first found value
      return e;
    } 
    else if (e.item) 
    //logic for showTree: true
    return findDeep(e.item, label);
  });
};

Data:

let testData = [
  {
    id: 1,
    label: 'parent1',
    item: [
      {
        id: 21,
        label: 'child1',
        item: [
          {
            id: 211,
            label: 'child31',
            item: [
              {
                id: 2111,
                label: 'child2211',
                item: [
                  {
                    id: 21111,
                    label: 'child22111'
                  }
                ]
              }
            ]
          },
          {
            id: 222,
            label: 'child32'
          }
        ]
      },
      {
        id: 22,
        label: 'child2',
        item: [
          {
            id: 221,
            label: 'child421',
            item: [
              {
                id: 2211,
                label: 'child2211'
              }
            ]
          },
          {
            id: 222,
            label: 'child222'
          }
        ]
      }
    ]
  },
  {
    id: 2,
    label: 'parent2',
    item: [
      {
        id: 21,
        label: 'child2',
        item: [
          {
            id: 511,
            label: 'child51',
            item: [
              {
                id: 5111,
                label: 'child5211',
                item: [
                  {
                    id: 51111,
                    label: 'child52111'
                  }
                ]
              }
            ]
          },
          {
            id: 522,
            label: 'child352'
          }
        ]
      }
    ]
  }
];

I would like to achieve something in the output

console.log(findDeep(testData, 'child3')[0]);

//output list
[
   {
      "id":1,
      "label":"parent1",
      "showTree": true,
      "item":[
         {
            "id":21,
            "label":"child1",
            "showTree": true,
            "item":[
               {
                  "id":211,
                  "label":"child31",
                  "selected" true,
                  "item":[
                     {
                        "id":2111,
                        "label":"child2211",
                        "item":[
                           {
                              "id":21111,
                              "label":"child22111"
                           }
                        ]
                     }
                  ]
               },
               {
                  "id":222,
                  "label":"child32"
               }
            ]
         },
         {
            "id":22,
            "label":"child2",
            "item":[
               {
                  "id":221,
                  "label":"child421",
                  "item":[
                     {
                        "id":2211,
                        "label":"child2211"
                     }
                  ]
               },
               {
                  "id":222,
                  "label":"child222"
               }
            ]
         }
      ]
   },
   {
      "id":2,
      "label":"parent2",
      "item":[
         {
            "id":21,
            "label":"child2",
            "item":[
               {
                  "id":511,
                  "label":"child51",
                  "item":[
                     {
                        "id":5111,
                        "label":"child5211",
                        "item":[
                           {
                              "id":51111,
                              "label":"child52111"
                           }
                        ]
                     }
                  ]
               },
               {
                  "id":522,
                  "label":"child352"
               }
            ]
         }
      ]
   }
]

//ouptput selected value

{
   "id":211,
   "label":"child31",
   "selected":true,
   "item":[
      {
         "id":2111,
         "label":"child2211",
         "item":[
            {
               "id":21111,
               "label":"child22111"
            }
         ]
      }
   ]
}

2 Answers

You can do a normal tree search, modify the found property, then pass true back up the tree towards the root, applying showTree: true along the way.

Note that this is an in-place approach, so the semantics are a little different than you show on your call. That's probably most appropriate for an algorithm like this that is just modifying a few properties on an existing structure rather than reallocating the whole thing from scratch. It's an antipattern to return the original structure for in-place algorithms as .sort() and .reverse() do for the purposes of chaining -- this can lead to surprising and subtle bugs.

const expandPath = (nodes, targetLabel) => {
  for (const node of nodes || []) {
    if (node.label.includes(targetLabel)) {
      return node.selected = true;
    }
    else if (expandPath(node.item, targetLabel)) {
      return node.showTree = true;
    }
  }
};

const testData = [ { id: 1, label: 'parent1', item: [ { id: 21, label: 'child1', item: [ { id: 211, label: 'child31', item: [ { id: 2111, label: 'child2211', item: [ { id: 21111, label: 'child22111' } ] } ] }, { id: 222, label: 'child32' } ] }, { id: 22, label: 'child2', item: [ { id: 221, label: 'child421', item: [ { id: 2211, label: 'child2211' } ] }, { id: 222, label: 'child222' } ] } ] }, { id: 2, label: 'parent2', item: [ { id: 21, label: 'child2', item: [ { id: 511, label: 'child51', item: [ { id: 5111, label: 'child5211', item: [ { id: 51111, label: 'child52111' } ] } ] }, { id: 522, label: 'child352' } ] } ] } ];

expandPath(testData, "child3");
console.log(testData[0]);

Note that the added properties are at the bottom of each node.

Also, in your original attempt, please avoid using map for in-place operations -- its purpose is to allocate a new array, not modify it. Use forEach instead.

I would suggest to use string methods, like

- `includes` or
- `startsWith`

along with the wanted parameter.

search = (array, type, value) => array.some(o => {
    if (o.label[type](value)) return o.selected = true;
    return search(o.item || [], type, value);
})
Related