Search code examples
javascriptarraysobjectreferencereduce

Change specific object value after array is flattened with reduce, how to?


I've got an nested array of objects, which could go multiple levels deep. For other purposes I had to flatten the array of objects to 1 level (non-nested) array. How can I update 1 specific object when I only know the id?

This is the example array of objects:

let items = [
  {
    id: 2,
    test: false,
    items: []
  },
  {
    id: 3,
    test: false,
    items: [
      {
        id: 4,
        test: false,
        items: [
          {
            id: 10,
            test: false,
            items: []
          },
        ]
      },
    ]
  },
  {
    id: 1,
    test: false,
    items: []
  },
];

I've used this flatten method from this thread: Deep Flatten JavaScript Object Recursively

function flatten(xs) {
  return xs.reduce((acc, x) => {
    acc = acc.concat(x);
    if (x.items) {
      acc = acc.concat(flatten(x.items));
      x.items = [];
    }
    return acc;
  }, []);
}

Let's say I want to update the test to true, only in id 10. What is the easiest way to do so on the original array? The reference is not linked anymore to the original array when I update the flattened array object.


Solution

  • Since your objects have a hard structure you can recursively iterate their items and find your target object:

    const findObject = (items, id) => {
      for(const item of items){
        if(item.id === id){
          return item;
        }
        if(item.items.length){
          const found = findObject(item.items, id);
          if(found) return found;
        }
      }
      return null;
    };
    
    const found = findObject(items, 10)
    found.test = true;
    
    console.log(items);
    <script>
    let items = [
      {
        id: 2,
        test: false,
        items: []
      },
      {
        id: 3,
        test: false,
        items: [
          {
            id: 4,
            test: false,
            items: [
              {
                id: 10,
                test: false,
                items: []
              },
            ]
          },
        ]
      },
      {
        id: 1,
        test: false,
        items: []
      },
    ];
    </script>

    A more advanced code would to have a function to walk your tree and update items.

    For example here we update item with id 3 and all its descendants to have test as true:

    const transformTree = (items, filterCb, updateCb, parents = []) => {
      for(const item of items){
        if(filterCb(item, parents)){
          if(false === updateCb(item, parents)){
            return false;
          }
        }
        if(item.items.length){
          if(false === transformTree(item.items, filterCb, updateCb, parents.concat(item))){
            return false;
          }
        }
      }
    };
    
    transformTree(items, 
      // filter id === 3 and all descendants
      (item, parents) => item.id === 3 || parents.some(parent => parent.id === 3), 
      // update test to true
      item => item.test = true
    );
    
    console.log(items);
    <script>
    let items = [
      {
        id: 2,
        test: false,
        items: []
      },
      {
        id: 3,
        test: false,
        items: [
          {
            id: 4,
            test: false,
            items: [
              {
                id: 10,
                test: false,
                items: []
              },
            ]
          },
        ]
      },
      {
        id: 1,
        test: false,
        items: []
      },
    ];
    </script>