Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deep merge instead of shallow merge?

Both Object.assign and Object spread only do a shallow merge.

An example of the problem:

// No object nesting const x = { a: 1 } const y = { b: 1 } const z = { ...x, ...y } // { a: 1, b: 1 } 

The output is what you'd expect. However if I try this:

// Object nesting const x = { a: { a: 1 } } const y = { a: { b: 1 } } const z = { ...x, ...y } // { a: { b: 1 } } 

Instead of

{ a: { a: 1, b: 1 } } 

you get

{ a: { b: 1 } } 

x is completely overwritten because the spread syntax only goes one level deep. This is the same with Object.assign().

Is there a way to do this?

like image 997
Mike Avatar asked Jan 14 '15 06:01

Mike


People also ask

What is a deep merge?

Deep merging ensures that all levels of the objects we merge into another object are copied instead of referencing the original objects.

Does object assign deep merge?

This is because Object. assign does a shallow merge and not a deep merge. A shallow merge means it will merge properties only at the first level and not the nested level.

Does spread Operator deep merge?

I recently shared how you can merge object properties with the spread operator but this method has one big limitation: the spread operator merge isn't a "deep" merge, meaning merges are recursive.

What is a shallow merge?

In a shallow merge, the properties of the first object are overwritten with the same property values of the second object.


1 Answers

I know this is a bit of an old issue but the easiest solution in ES2015/ES6 I could come up with was actually quite simple, using Object.assign(),

Hopefully this helps:

/**  * Simple object check.  * @param item  * @returns {boolean}  */ export function isObject(item) {   return (item && typeof item === 'object' && !Array.isArray(item)); }  /**  * Deep merge two objects.  * @param target  * @param ...sources  */ export function mergeDeep(target, ...sources) {   if (!sources.length) return target;   const source = sources.shift();    if (isObject(target) && isObject(source)) {     for (const key in source) {       if (isObject(source[key])) {         if (!target[key]) Object.assign(target, { [key]: {} });         mergeDeep(target[key], source[key]);       } else {         Object.assign(target, { [key]: source[key] });       }     }   }    return mergeDeep(target, ...sources); } 

Example usage:

mergeDeep(this, { a: { b: { c: 123 } } }); // or const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});   console.dir(merged); // { a: 1, b: { c: { d: [Object] } } } 

You'll find an immutable version of this in the answer below.

Note that this will lead to infinite recursion on circular references. There's some great answers on here on how to detect circular references if you think you'd face this issue.

like image 102
Salakar Avatar answered Sep 20 '22 15:09

Salakar