Home > Software design >  how to add object in nested array of objects without mutating original source
how to add object in nested array of objects without mutating original source

Time:05-26

I have multi nested array of objects, in which I need to merge objects in the grandChildren based on the id without mutation

Details ......

###Example

  let arr1 = {
  "initiateLevel": true,
  "parent": [
    {
      "name": "level1",
      "childrens": [
        {
          "group": "Level-group",
          "grandChildrens": [
            {
              "id": 21,
              "technology": "sp1",
              "path": "l2"
            },
            {
              "id": 22,
              "technology": "sp2",
              "path": "l2"
            }
          ]
        }
      ]
    },
    {
      "name": "level2",
      "childrens": [
        {
          "group": "Level-group-2",
          "grandChildrens": [
            {
              "id": 121,
              "technology": "sp12",
              "path": "l4"
            },
            {
              "id": 122,
              "technology": "sp22",
              "path": "l4"
            }
          ]
        }
      ]
    }
  ]
}

ex: Below object needs to merged with the array based on the id

 let newobj=  
 [
      {
        "id": 22,
        "reason": "reason 2",
        "phase": "phase 2",
        "reviewer": "by user 2",
        "date": "date 2"
      },
      {
        "id": 21,
        "reason": "reason 1",
        "phase": "phase 1",
        "reviewer": "by user 1",
        "date": "date 1"
      }
    ]

expected output:

{
  "initiateLevel": true,
  "parent": [
    {
      "name": "level1",
      "childrens": [
        {
          "group": "Level-group",
          "grandChildrens": [
            {
              "id": 21,
              "technology": "sp1",
              "path": "l2",
              "reason": "reason 1",
              "phase": "phase 1",
              "reviewer": "by user 1",
              "date": "date 1"
            },
            {
              "id": 22,
              "technology": "sp2",
              "path": "l2",
              "reason": "reason 2",
              "phase": "phase 2",
              "reviewer": "by user 2",
              "date": "date 2"
            }
          ]
        }
      ]
    },
    {
      "name": "level2",
      "childrens": [
        {
          "group": "Level-group-2",
          "grandChildrens": [
            {
              "id": 121,
              "technology": "sp12",
              "path": "l4"
            },
            {
              "id": 122,
              "technology": "sp22",
              "path": "l4"
            }
          ]
        }
      ]
    }
  ]
}

I tried to like this. but it's not working

const merge = (y, z) => {
  y.parent.forEach((element) => {
    element.childrens.forEach((x) => {
      x.grandChildrens.forEach((test) => {
        const reviewIndex = z.findIndex(
          (reviewItem) => reviewItem.id === test.id
        );
        if(reviewIndex>=0)
        {
         return  {...test, ...z[reviewIndex]}  
        }
        
      });
    });
  });
};

merge(arr1,newobj)

How to merge the object based on the id without mutation.

CodePudding user response:

From question:

How to merge the object based on the id without mutation.

Simple answer: "NO". It is not possible to "merge" (ie, update) an object without updating (ie, mutating).

Alternative solution: Create a copy (preferably deep-clone) of the object which will be updated / merged. Thus, the original object will remain unmodified.

Presented below is one possible way to achieve the alternative solution.

Code Snippet

// recursive method to find & update 
// grandChildrens' array's elements matching "id"
const findAndUpdate = (needle, hayStack) => (
  // iterate over key-value pairs of object
  Object.entries(hayStack)
  .forEach(([k, v]) => {
    // if value is an array
    if (v && Array.isArray(v)) {
      // if the array corresponds to "grandChildrens"
      if (k === 'grandChildrens') {
        // update elements which match "id"
        v = v.map(
          ({ id, ...rest }) => {
            if (id === needle.id) {
              return ({
                id, ...rest, ...needle
              })
            };
            return ({ id, ...rest })
          }
        );
        // mutate "hayStack" with updated element
        hayStack[k] = v;
      } else {
        // array is not "grandChildren"
        // simply iterate over each object in array
        v.forEach(
          obj => findAndUpdate(needle, obj)
        );
      }
    };
  })
);

// method to generate a new object
// with merged information
const generateMergedObject = (ob, ar) => {
  // first deep-clone the parameter "ob"
  // this prevents "ob" from being changed
  const resObj = structuredClone(ob);
  
  // for each elt in "ar" array
  // find and update "grandChildrens" array
  // based on matching "id"
  ar.forEach(obj => {
    findAndUpdate(obj, resObj);
  });
  
  // return the result object
  return resObj;
};

// the main object which will remain unmodified
const parentObj = {
  "initiateLevel": true,
  "parent": [{
      "name": "level1",
      "childrens": [{
        "group": "Level-group",
        "grandChildrens": [{
            "id": 21,
            "technology": "sp1",
            "path": "l2"
          },
          {
            "id": 22,
            "technology": "sp2",
            "path": "l2"
          }
        ]
      }]
    },
    {
      "name": "level2",
      "childrens": [{
        "group": "Level-group-2",
        "grandChildrens": [{
            "id": 121,
            "technology": "sp12",
            "path": "l4"
          },
          {
            "id": 122,
            "technology": "sp22",
            "path": "l4"
          }
        ]
      }]
    }
  ]
};

// the info that will be used to update a copy of the parentObj
const updatesArr = [{
    "id": 22,
    "reason": "reason 2",
    "phase": "phase 2",
    "reviewer": "by user 2",
    "date": "date 2"
  },
  {
    "id": 21,
    "reason": "reason 1",
    "phase": "phase 1",
    "reviewer": "by user 1",
    "date": "date 1"
  }
];

// invoke the method to create the new object
// and display the original object that remains as-is
console.log(
  'generate new object with updated info...',
  generateMergedObject(parentObj, updatesArr),
  '\n\n original object remains unmodified: ',
  parentObj
);
.as-console-wrapper { max-height: 100% !important; top: 0 }

Explanation

Inline comments added to the snippet above.

CodePudding user response:

Use Array.prototype.map instead, forEach gets no returns

let newobj = [
  {
    id: 22,
    reason: 'reason 2',
    phase: 'phase 2',
    reviewer: 'by user 2',
    date: 'date 2'
  },
  {
    id: 21,
    reason: 'reason 1',
    phase: 'phase 1',
    reviewer: 'by user 1',
    date: 'date 1'
  }
];

let arr1 = {
  initiateLevel: true,
  parent: [
    {
      name: 'level1',
      childrens: [
        {
          group: 'Level-group',
          grandChildrens: [
            {
              id: 21,
              technology: 'sp1',
              path: 'l2',
              reason: 'reason 1',
              phase: 'phase 1',
              reviewer: 'by user 1',
              date: 'date 1'
            },
            {
              id: 22,
              technology: 'sp2',
              path: 'l2',
              reason: 'reason 2',
              phase: 'phase 2',
              reviewer: 'by user 2',
              date: 'date 2'
            }
          ]
        }
      ]
    },
    {
      name: 'level2',
      childrens: [
        {
          group: 'Level-group-2',
          grandChildrens: [
            {
              id: 121,
              technology: 'sp12',
              path: 'l4'
            },
            {
              id: 122,
              technology: 'sp22',
              path: 'l4'
            }
          ]
        }
      ]
    }
  ]
};

const merge = (y, z) => {
  const parent = y.parent.map((element) => {
    return { 
      ...element, 
      childrens: element.childrens.map((x) => {
        return {
          ...x,
          grandChildrens: x.grandChildrens.map((test) => {
            return { ...test, ...z.find((reviewItem) => reviewItem.id === test.id) };
           })
         };
       })
     }
  });
  return { ...y, parent }; 
};

const arr2 = merge(arr1, newobj);
console.log(arr2);

  • Related