Related
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?
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.
You can use Lodash merge:
var object = {
'a': [{ 'b': 2 }, { 'd': 4 }]
};
var other = {
'a': [{ 'c': 3 }, { 'e': 5 }]
};
console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
The problem is non-trivial when it comes to host objects or any kind of object that's more complex than a bag of values
do you invoke a getter to obtain a value or do you copy over the property descriptor?
what if the merge target has a setter (either own property or in its prototype chain)? Do you consider the value as already-present or call the setter to update the current value?
do you invoke own-property functions or copy them over? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined?
what if it's something like a DOM node? You certainly don't want to treat it as simple object and just deep-merge all its properties over into
how to deal with "simple" structures like arrays or maps or sets? Consider them already-present or merge them too?
how to deal with non-enumerable own properties?
what about new subtrees? Simply assign by reference or deep clone?
how to deal with frozen/sealed/non-extensible objects?
Another thing to keep in mind: Object graphs that contain cycles. It's usually not difficult to deal with - simply keep a Set of already-visited source objects - but often forgotten.
You probably should write a deep-merge function that only expects primitive values and simple objects - at most those types that the structured clone algorithm can handle - as merge sources. Throw if it encounters anything it cannot handle or just assign by reference instead of deep merging.
In other words, there is no one-size-fits-all algorithm, you either have to roll your own or look for a library method that happens to cover your use-cases.
Here is an immutable (does not modify the inputs) version of #Salakar's answer. Useful if you're doing functional programming type stuff.
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
export default function mergeDeep(target, source) {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
Update 2022:
I created mergician to address the various merge/clone requirements discussed in the comments. It is based on the same concept as my original answer (below) but offers configurable options:
Unlike native methods and other merge/clone utilities, Mergician provides advanced options for customizing the merge/clone process. These options make it easy to inspect, filter, and modify keys and properties; merge or skip unique, common, and universal keys (i.e., intersections, unions, and differences); and merge, sort, and remove duplicates from arrays. Property accessors and descriptors are also handled properly, ensuring that getter/setter functions are retained and descriptor values are defined on new merged/cloned objects.
Notably, mergician is significantly smaller (1.5k min+gzip) than similar utilities like lodash.merge (5.1k min+gzip).
GitHub: https://github.com/jhildenbiddle/mergician
NPM: https://www.npmjs.com/package/mergician
Docs: https://jhildenbiddle.github.io/mergician/
Original answer:
Since this issue is still active, here's another approach:
ES6/2015
Immutable (does not modify original objects)
Handles arrays (concatenates them)
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* #param {...object} objects - Objects to merge
* #returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
// Test objects
const obj1 = {
a: 1,
b: 1,
c: { x: 1, y: 1 },
d: [ 1, 1 ]
}
const obj2 = {
b: 2,
c: { y: 2, z: 2 },
d: [ 2, 2 ],
e: 2
}
const obj3 = mergeDeep(obj1, obj2);
// Out
console.log(obj3);
I know there's a lot of answers already and as many comments arguing they won't work. The only consensus is that it's so complicated that nobody made a standard for it. However, most of accepted answers in SO expose "simple tricks" that are widely used. So, for all of us like me who are no experts but want to write safer code by grasping a little more about javascript's complexity, I'll try to shed some light.
Before getting our hands dirty, let me clarify 2 points:
[DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either.
I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.
Answers with for..in or Object.keys are misleading
Making a deep copy seems so basic and common practice that we expect to find a one-liner or, at least, a quick win via simple recursion. We don't expect we should need a library or write a custom function of 100 lines.
When I first read Salakar's answer, I genuinely thought I could do better and simpler (you can compare it with Object.assign on x={a:1}, y={a:{b:1}}). Then I read the8472's answer and I thought... there is no getting away so easily, improving already given answers won't get us far.
Let's let deep copy and recursive aside an instant. Just consider how (wrongly) people parse properties to copy a very simple object.
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Object.keys will omit own non-enumerable properties, own symbol-keyed properties and all prototype's properties. It may be fine if your objects don't have any of those. But keep it mind that Object.assign handles own symbol-keyed enumerable properties. So your custom copy lost its bloom.
for..in will provide properties of the source, of its prototype and of the full prototype chain without you wanting it (or knowing it). Your target may end up with too many properties, mixing up prototype properties and own properties.
If you're writing a general purpose function and you're not using Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbols or Object.getPrototypeOf, you're most probably doing it wrong.
Things to consider before writing your function
First, make sure you understand what a Javascript object is. In Javascript, an object is made of its own properties and a (parent) prototype object. The prototype object in turn is made of its own properties and a prototype object. And so on, defining a prototype chain.
A property is a pair of key (string or symbol) and descriptor (value or get/set accessor, and attributes like enumerable).
Finally, there are many types of objects. You may want to handle differently an object Object from an object Date or an object Function.
So, writing your deep copy, you should answer at least those questions:
What do I consider deep (proper for recursive look up) or flat?
What properties do I want to copy? (enumerable/non-enumerable, string-keyed/symbol-keyed, own properties/prototype's own properties, values/descriptors...)
For my example, I consider that only the object Objects are deep, because other objects created by other constructors may not be proper for an in-depth look. Customized from this SO.
function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
And I made an options object to choose what to copy (for demo purpose).
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Proposed function
You can test it in this plunker.
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
That can be used like this:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
If you want to have a one liner without requiring a huge library like lodash, I suggest you to use deepmerge (npm install deepmerge) or deepmerge-ts (npm install deepmerge-ts).
deepmerge also comes with typings for TypeScript and is more stable (since it's older), but deepmerge-ts is also available for Deno and is faster by design, although written in TypeScript as the name implies.
Once imported you can do
deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });
to get
{ a: 2, b: 2, c: 3, d: 3 }
This works nicely with complex objects and arrays. A real all-rounder solution this is.
I use lodash:
import _ = require('lodash');
value = _.merge(value1, value2);
Many answers use tens of lines of code, or require adding a new library to the project, but if you use recursion, this is just 4 lines of code.
function merge(current, updates) {
for (key of Object.keys(updates)) {
if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
else merge(current[key], updates[key]);
}
return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));
Arrays handling: The above version overwrites old array values with new ones. If you want it to keep the old array values and add the new ones, just add a else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key]) block above the else statament and you're all set.
Here, straight forward;
a simple solution that works like Object.assign just deep, and works for an array, without any modification.
function deepAssign(target, ...sources) {
for (source of sources) {
for (let k in source) {
let vs = source[k], vt = target[k]
if (Object(vs) == vs && Object(vt) === vt) {
target[k] = deepAssign(vt, vs)
continue
}
target[k] = source[k]
}
}
return target
}
x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)
console.log(JSON.stringify(x) === JSON.stringify({
"a": {
"a": 1,
"b": 1
},
"b": [ 1, 2, null, 4 ],
"c": 3
}))
Edit:
I answer somewhere else about a new method for deep comparing 2 objects.
that method can use also for a deep merging. If you want implantation put a comment
https://stackoverflow.com/a/71177790/1919821
Here is TypeScript implementation:
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (source === undefined) {
return target;
}
if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach(function(key: string) {
if (isMergebleObject(source[key])) {
if (!target[key]) {
target[key] = {};
}
mergeObjects(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return mergeObjects(target, ...sources);
};
const isObject = (item: any): boolean => {
return item !== null && typeof item === 'object';
};
const isMergebleObject = (item): boolean => {
return isObject(item) && !Array.isArray(item);
};
And Unit Tests:
describe('merge', () => {
it('should merge Objects and all nested Ones', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
});
it('should behave like Object.assign on the top level', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C'};
const obj2 = { a: undefined, b: { b1: 'B1'}};
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
});
it('should not merge array values, just override', () => {
const obj1 = {a: ['A', 'B']};
const obj2 = {a: ['C'], b: ['D']};
expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
});
it('typed merge', () => {
expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
.toEqual(new TestPosition(1, 1));
});
});
class TestPosition {
constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
The deepmerge npm package appears to be the most widely used library for solving this problem:
https://www.npmjs.com/package/deepmerge
I would like to present a pretty simple ES5 alternative. The function gets 2 parameters - target and source that must be of type "object". Target will be the resulting object. Target keeps all its original properties but their values may be modified though.
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
if(prop in target) { // handling merging of two properties with equal names
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else { // two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else { // new properties get added to target
target[prop] = source[prop];
}
}
return target;
}
cases:
if target doesn't have a source property, target gets it;
if target does have a source property and target & source are not
both objects (3 cases out of 4), target's property gets overriden;
if target does have a source property and both of them are objects/arrays (1 remaining case), then recursion happens merging two objects (or concatenation of two arrays);
also consider the following:
array + obj = array
obj + array = obj
obj + obj = obj (recursively merged)
array + array = array (concat)
It is predictable, supports primitive types as well as arrays and objects. Also as we can merge 2 objects, I think that we can merge more than 2 via reduce function.
take a look at an example (and play around with it if you want):
var a = {
"a_prop": 1,
"arr_prop": [4, 5, 6],
"obj": {
"a_prop": {
"t_prop": 'test'
},
"b_prop": 2
}
};
var b = {
"a_prop": 5,
"arr_prop": [7, 8, 9],
"b_prop": 15,
"obj": {
"a_prop": {
"u_prop": false
},
"b_prop": {
"s_prop": null
}
}
};
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false;
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue;
if(prop in target) {
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
console.log(deepMerge(a, b));
There is a limitation - browser's call stack length. Modern browsers will throw an error at some really deep level of recursion (think of thousands of nested calls). Also you are free to treat situations like array + object etc. as you wish by adding new conditions and type checks.
A simple solution with ES5 (overwrite existing value):
function merge(current, update) {
Object.keys(update).forEach(function(key) {
// if update[key] exist, and it's not a string or array,
// we go in one level deeper
if (current.hasOwnProperty(key)
&& typeof current[key] === 'object'
&& !(current[key] instanceof Array)) {
merge(current[key], update[key]);
// if update[key] doesn't exist in current, or it's a string
// or array, then assign/overwrite current[key] to update[key]
} else {
current[key] = update[key];
}
});
return current;
}
var x = { a: { a: 1 } }
var y = { a: { b: 1 } }
console.log(merge(x, y));
Is there a way to do this?
If npm libraries can be used as a solution, object-merge-advanced from yours truly allows to merge objects deeply and customise/override every single merge action using a familiar callback function. The main idea of it is more than just deep merging — what happens with the value when two keys are the same? This library takes care of that — when two keys clash, object-merge-advanced weighs the types, aiming to retain as much data as possible after merging:
First input argument's key is marked #1, second argument's — #2. Depending on each type, one is chosen for the result key's value. In diagram, "an object" means a plain object (not array etc).
When keys don't clash, they all enter the result.
From your example snippet, if you used object-merge-advanced to merge your code snippet:
const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
// a: {
// a: 1,
// b: 1
// }
// }
It's algorithm recursively traverses all input object keys, compares and builds and returns the new merged result.
The following function makes a deep copy of objects, it covers copying primitive, arrays as well as object
function mergeDeep (target, source) {
if (typeof target == "object" && typeof source == "object") {
for (const key in source) {
if (source[key] === null && (target[key] === undefined || target[key] === null)) {
target[key] = null;
} else if (source[key] instanceof Array) {
if (!target[key]) target[key] = [];
//concatenate arrays
target[key] = target[key].concat(source[key]);
} else if (typeof source[key] == "object") {
if (!target[key]) target[key] = {};
this.mergeDeep(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
Most examples here seem too complex, I'm using one in TypeScript I created, I think it should cover most cases (I'm handling arrays as regular data, just replacing them).
const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);
export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
const isDeep = (prop: string) =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...(target as Object),
...(replaced as Object)
} as A & B;
};
Same thing in plain JS, just in case:
const isObject = item => typeof item === 'object' && !Array.isArray(item);
const merge = (target, source) => {
const isDeep = prop =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...target,
...replaced
};
};
Here are my test cases to show how you could use it
describe('merge', () => {
context('shallow merges', () => {
it('merges objects', () => {
const a = { a: 'discard' };
const b = { a: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test' });
});
it('extends objects', () => {
const a = { a: 'test' };
const b = { b: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
});
it('extends a property with an object', () => {
const a = { a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
it('replaces a property with an object', () => {
const a = { b: 'whatever', a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
});
context('deep merges', () => {
it('merges objects', () => {
const a = { test: { a: 'discard', b: 'test' } };
const b = { test: { a: 'test' } } ;
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends objects', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: 'test' } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends a property with an object', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
it('replaces a property with an object', () => {
const a = { test: { b: 'whatever', a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
});
});
Please let me know if you think I'm missing some functionality.
We can use $.extend(true,object1,object2) for deep merging. Value true denotes merge two objects recursively, modifying the first.
$extend(true,target,object)
If your are using ImmutableJS you can use mergeDeep :
fromJS(options).mergeDeep(options2).toJS();
Ramda which is a nice library of javascript functions has mergeDeepLeft and mergeDeepRight. Any of these work pretty well for this problem. Please take a look on the documentation here: https://ramdajs.com/docs/#mergeDeepLeft
For the specific example in question we can use:
import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
with reduce
export const merge = (objFrom, objTo) => Object.keys(objFrom)
.reduce(
(merged, key) => {
merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
? merge(objFrom[key], merged[key] ?? {})
: objFrom[key]
return merged
}, { ...objTo }
)
test('merge', async () => {
const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
const obj3 = merge3(obj1, obj2)
expect(obj3).toEqual(
{ par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
)
})
I was having this issue when loading a cached redux state. If I just load the cached state, I'd run into errors for new app version with an updated state structure.
It was already mentioned, that lodash offers the merge function, which I used:
const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
I didn't like any of the existing solutions. So, I went ahead and wrote my own.
Object.prototype.merge = function(object) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
if (typeof this[key] === "object" && typeof object[key] === "object") {
this[key].merge(object[key]);
continue;
}
this[key] = object[key];
}
}
return this;
}
I hope this helps those of you who struggle to understand what's going on. I've seen a lot of meaningless variables being used on here.
Thanks
Here's another one I just wrote that supports arrays. It concats them.
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function isPlainObject(obj) {
return isObject(obj) && (
obj.constructor === Object // obj = {}
|| obj.constructor === undefined // obj = Object.create(null)
);
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if(Array.isArray(target)) {
if(Array.isArray(source)) {
target.push(...source);
} else {
target.push(source);
}
} else if(isPlainObject(target)) {
if(isPlainObject(source)) {
for(let key of Object.keys(source)) {
if(!target[key]) {
target[key] = source[key];
} else {
mergeDeep(target[key], source[key]);
}
}
} else {
throw new Error(`Cannot merge object with non-object`);
}
} else {
target = source;
}
return mergeDeep(target, ...sources);
};
Use this function:
merge(target, source, mutable = false) {
const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
for (const prop in source) {
if (target[prop] == null || typeof target[prop] === 'undefined') {
newObj[prop] = source[prop];
} else if (Array.isArray(target[prop])) {
newObj[prop] = source[prop] || target[prop];
} else if (target[prop] instanceof RegExp) {
newObj[prop] = source[prop] || target[prop];
} else {
newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
}
}
return newObj;
}
This is a cheap deep merge that uses as little code as I could think of. Each source overwrites the previous property when it exists.
const { keys } = Object;
const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
isObject(a) && isObject(b)
? deepMerge(a, b)
: isObject(a) && !isObject(b)
? a
: b;
const coalesceByKey = source => (acc, key) =>
(acc[key] && source[key]
? (acc[key] = merge(acc[key], source[key]))
: (acc[key] = source[key])) && acc;
/**
* Merge all sources into the target
* overwriting primitive values in the the accumulated target as we go (if they already exist)
* #param {*} target
* #param {...any} sources
*/
const deepMerge = (target, ...sources) =>
sources.reduce(
(acc, source) => keys(source).reduce(coalesceByKey(source), acc),
target
);
console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
for (const prop in source) {
if (!source.hasOwnProperty(prop)) {
continue;
}
if (source[prop] === null) {
// property is null
dest[prop] = source[prop];
continue;
}
if (typeof source[prop] === 'object') {
// if property is object let's dive into in
if (Array.isArray(source[prop])) {
dest[prop] = [];
} else {
if (!dest.hasOwnProperty(prop)
|| typeof dest[prop] !== 'object'
|| dest[prop] === null || Array.isArray(dest[prop])
|| !Object.keys(dest[prop]).length) {
dest[prop] = {};
}
}
recursivelyMoveProperties(source[prop], dest[prop]);
continue;
}
// property is simple type: string, number, e.t.c
dest[prop] = source[prop];
}
return dest;
}
Unit test:
describe('recursivelyMoveProperties', () => {
it('should copy properties correctly', () => {
const source: any = {
propS1: 'str1',
propS2: 'str2',
propN1: 1,
propN2: 2,
propA1: [1, 2, 3],
propA2: [],
propB1: true,
propB2: false,
propU1: null,
propU2: null,
propD1: undefined,
propD2: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subN1: 21,
subN2: 22,
subA1: [21, 22, 23],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
let dest: any = {
propS2: 'str2',
propS3: 'str3',
propN2: -2,
propN3: 3,
propA2: [2, 2],
propA3: [3, 2, 1],
propB2: true,
propB3: false,
propU2: 'not null',
propU3: null,
propD2: 'defined',
propD3: undefined,
propO2: {
subS2: 'inv22',
subS3: 'sub23',
subN2: -22,
subN3: 23,
subA2: [5, 5, 5],
subA3: [31, 32, 33],
subB2: false,
subB3: true,
subU2: 'not null --- ',
subU3: null,
subD2: ' not undefined ----',
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
dest = recursivelyMoveProperties(source, dest);
expect(dest).toEqual({
propS1: 'str1',
propS2: 'str2',
propS3: 'str3',
propN1: 1,
propN2: 2,
propN3: 3,
propA1: [1, 2, 3],
propA2: [],
propA3: [3, 2, 1],
propB1: true,
propB2: false,
propB3: false,
propU1: null,
propU2: null,
propU3: null,
propD1: undefined,
propD2: undefined,
propD3: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subS3: 'sub23',
subN1: 21,
subN2: 22,
subN3: 23,
subA1: [21, 22, 23],
subA2: [],
subA3: [31, 32, 33],
subB1: false,
subB2: true,
subB3: true,
subU1: null,
subU2: null,
subU3: null,
subD1: undefined,
subD2: undefined,
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
});
});
});
Another variation using recursion, hope you find it useful.
const merge = (obj1, obj2) => {
const recursiveMerge = (obj, entries) => {
for (const [key, value] of entries) {
if (typeof value === "object") {
obj[key] = obj[key] ? {...obj[key]} : {};
recursiveMerge(obj[key], Object.entries(value))
else {
obj[key] = value;
}
}
return obj;
}
return recursiveMerge(obj1, Object.entries(obj2))
}
My use case for this was to merge default values into a configuration. If my component accepts a configuration object that has a deeply nested structure, and my component defines a default configuration, I wanted to set default values in my configuration for all configuration options that were not supplied.
Example usage:
export default MyComponent = ({config}) => {
const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
// Component code here
}
This allows me to pass an empty or null config, or a partial config and have all of the values that are not configured fall back to their default values.
My implementation of mergeDefaults looks like this:
export default function mergeDefaults(config, defaults) {
if (config === null || config === undefined) return defaults;
for (var attrname in defaults) {
if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
}
return config;
}
And these are my unit tests
import '#testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';
describe('mergeDefaults', () => {
it('should create configuration', () => {
const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should fill configuration', () => {
const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should not overwrite configuration', () => {
const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('config1');
expect(config.b.d).toStrictEqual('config2');
});
it('should merge configuration', () => {
const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('config2');
expect(config.e).toStrictEqual(15);
});
});
Sometimes you don't need deep merge, even if you think so. For example, if you have a default config with nested objects and you want to extend it deeply with your own config, you can create a class for that. The concept is very simple:
function AjaxConfig(config) {
// Default values + config
Object.assign(this, {
method: 'POST',
contentType: 'text/plain'
}, config);
// Default values in nested objects
this.headers = Object.assign({}, this.headers, {
'X-Requested-With': 'custom'
});
}
// Define your config
var config = {
url: 'https://google.com',
headers: {
'x-client-data': 'CI22yQEI'
}
};
// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);
// View in DevTools
console.log(fullMergedConfig);
You can convert it to a function (not a constructor).
Given an object like this:
const spacing = {
xxs: "0.25rem",
xs: "0.5rem",
sm: "0.75rem",
md: "1rem",
lg: "1.5rem",
"2xl": "2rem",
"3xl": "3rem",
oggetto: {
"1": 1,
"2": 2,
},
}
I'm trying to write a function that loops through my object, and checks if the property of this object is another nested object, like oggetto. In that case, I'd like to recursively call the function. My main goal is to retrieve all the keys of the main and any nested object. I'd share some code but I don't really have an idea on how to approach this. Thank you!
function getObjectKeys(o, keys = []) {
Object.entries(o).forEach(([key, value]) => {
if (typeof value === "object") {
getObjectKeys(value, keys);
} else {
keys.push(key);
}
});
return keys;
}
getObjectKeys(spacing); // [ "xxs", "xs", "sm", "md", "lg", "2xl", "3xl", "1", "2" ]
first you must consider arrays when using typeof since typeof of an array and an object both return 'object'.
then you can have a recursive function to loop through the keys and return them if they hold an object.
for example something like this will return an array of arrays which hold the object key in the first index and full path to access the object in the second index:
const obj = {
n: 1,
s: 'test',
b: false,
a: [],
obj1: {
n: 2
},
obj2: {
innerObj: {
b: true,
otherInnerObj: {
s: 'hi'
},
},
a: []
},
obj3: {}
};
function findObjects(obj, parent, pathArr = []) {
for (k in obj) {
const v = obj[k];
if (typeof v === 'object' && !Array.isArray(v)) {
const parentName = `${parent}/${k}`;
pathArr.push([k, parentName]);
findObjects(v, parentName, pathArr);
}
}
return pathArr;
}
const result = findObjects(obj, 'obj');
const onlyKeys = result.map(x => x[0]);
const onlyPaths = result.map(x => x[1]);
console.log(onlyKeys);
console.log(onlyPaths);
console.log(result);
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?
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.
You can use Lodash merge:
var object = {
'a': [{ 'b': 2 }, { 'd': 4 }]
};
var other = {
'a': [{ 'c': 3 }, { 'e': 5 }]
};
console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
The problem is non-trivial when it comes to host objects or any kind of object that's more complex than a bag of values
do you invoke a getter to obtain a value or do you copy over the property descriptor?
what if the merge target has a setter (either own property or in its prototype chain)? Do you consider the value as already-present or call the setter to update the current value?
do you invoke own-property functions or copy them over? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined?
what if it's something like a DOM node? You certainly don't want to treat it as simple object and just deep-merge all its properties over into
how to deal with "simple" structures like arrays or maps or sets? Consider them already-present or merge them too?
how to deal with non-enumerable own properties?
what about new subtrees? Simply assign by reference or deep clone?
how to deal with frozen/sealed/non-extensible objects?
Another thing to keep in mind: Object graphs that contain cycles. It's usually not difficult to deal with - simply keep a Set of already-visited source objects - but often forgotten.
You probably should write a deep-merge function that only expects primitive values and simple objects - at most those types that the structured clone algorithm can handle - as merge sources. Throw if it encounters anything it cannot handle or just assign by reference instead of deep merging.
In other words, there is no one-size-fits-all algorithm, you either have to roll your own or look for a library method that happens to cover your use-cases.
Here is an immutable (does not modify the inputs) version of #Salakar's answer. Useful if you're doing functional programming type stuff.
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
export default function mergeDeep(target, source) {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
Update 2022:
I created mergician to address the various merge/clone requirements discussed in the comments. It is based on the same concept as my original answer (below) but offers configurable options:
Unlike native methods and other merge/clone utilities, Mergician provides advanced options for customizing the merge/clone process. These options make it easy to inspect, filter, and modify keys and properties; merge or skip unique, common, and universal keys (i.e., intersections, unions, and differences); and merge, sort, and remove duplicates from arrays. Property accessors and descriptors are also handled properly, ensuring that getter/setter functions are retained and descriptor values are defined on new merged/cloned objects.
Notably, mergician is significantly smaller (1.5k min+gzip) than similar utilities like lodash.merge (5.1k min+gzip).
GitHub: https://github.com/jhildenbiddle/mergician
NPM: https://www.npmjs.com/package/mergician
Docs: https://jhildenbiddle.github.io/mergician/
Original answer:
Since this issue is still active, here's another approach:
ES6/2015
Immutable (does not modify original objects)
Handles arrays (concatenates them)
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* #param {...object} objects - Objects to merge
* #returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
// Test objects
const obj1 = {
a: 1,
b: 1,
c: { x: 1, y: 1 },
d: [ 1, 1 ]
}
const obj2 = {
b: 2,
c: { y: 2, z: 2 },
d: [ 2, 2 ],
e: 2
}
const obj3 = mergeDeep(obj1, obj2);
// Out
console.log(obj3);
I know there's a lot of answers already and as many comments arguing they won't work. The only consensus is that it's so complicated that nobody made a standard for it. However, most of accepted answers in SO expose "simple tricks" that are widely used. So, for all of us like me who are no experts but want to write safer code by grasping a little more about javascript's complexity, I'll try to shed some light.
Before getting our hands dirty, let me clarify 2 points:
[DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either.
I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.
Answers with for..in or Object.keys are misleading
Making a deep copy seems so basic and common practice that we expect to find a one-liner or, at least, a quick win via simple recursion. We don't expect we should need a library or write a custom function of 100 lines.
When I first read Salakar's answer, I genuinely thought I could do better and simpler (you can compare it with Object.assign on x={a:1}, y={a:{b:1}}). Then I read the8472's answer and I thought... there is no getting away so easily, improving already given answers won't get us far.
Let's let deep copy and recursive aside an instant. Just consider how (wrongly) people parse properties to copy a very simple object.
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Object.keys will omit own non-enumerable properties, own symbol-keyed properties and all prototype's properties. It may be fine if your objects don't have any of those. But keep it mind that Object.assign handles own symbol-keyed enumerable properties. So your custom copy lost its bloom.
for..in will provide properties of the source, of its prototype and of the full prototype chain without you wanting it (or knowing it). Your target may end up with too many properties, mixing up prototype properties and own properties.
If you're writing a general purpose function and you're not using Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbols or Object.getPrototypeOf, you're most probably doing it wrong.
Things to consider before writing your function
First, make sure you understand what a Javascript object is. In Javascript, an object is made of its own properties and a (parent) prototype object. The prototype object in turn is made of its own properties and a prototype object. And so on, defining a prototype chain.
A property is a pair of key (string or symbol) and descriptor (value or get/set accessor, and attributes like enumerable).
Finally, there are many types of objects. You may want to handle differently an object Object from an object Date or an object Function.
So, writing your deep copy, you should answer at least those questions:
What do I consider deep (proper for recursive look up) or flat?
What properties do I want to copy? (enumerable/non-enumerable, string-keyed/symbol-keyed, own properties/prototype's own properties, values/descriptors...)
For my example, I consider that only the object Objects are deep, because other objects created by other constructors may not be proper for an in-depth look. Customized from this SO.
function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
And I made an options object to choose what to copy (for demo purpose).
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Proposed function
You can test it in this plunker.
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
That can be used like this:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
If you want to have a one liner without requiring a huge library like lodash, I suggest you to use deepmerge (npm install deepmerge) or deepmerge-ts (npm install deepmerge-ts).
deepmerge also comes with typings for TypeScript and is more stable (since it's older), but deepmerge-ts is also available for Deno and is faster by design, although written in TypeScript as the name implies.
Once imported you can do
deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });
to get
{ a: 2, b: 2, c: 3, d: 3 }
This works nicely with complex objects and arrays. A real all-rounder solution this is.
I use lodash:
import _ = require('lodash');
value = _.merge(value1, value2);
Many answers use tens of lines of code, or require adding a new library to the project, but if you use recursion, this is just 4 lines of code.
function merge(current, updates) {
for (key of Object.keys(updates)) {
if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
else merge(current[key], updates[key]);
}
return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));
Arrays handling: The above version overwrites old array values with new ones. If you want it to keep the old array values and add the new ones, just add a else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key]) block above the else statament and you're all set.
Here, straight forward;
a simple solution that works like Object.assign just deep, and works for an array, without any modification.
function deepAssign(target, ...sources) {
for (source of sources) {
for (let k in source) {
let vs = source[k], vt = target[k]
if (Object(vs) == vs && Object(vt) === vt) {
target[k] = deepAssign(vt, vs)
continue
}
target[k] = source[k]
}
}
return target
}
x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)
console.log(JSON.stringify(x) === JSON.stringify({
"a": {
"a": 1,
"b": 1
},
"b": [ 1, 2, null, 4 ],
"c": 3
}))
Edit:
I answer somewhere else about a new method for deep comparing 2 objects.
that method can use also for a deep merging. If you want implantation put a comment
https://stackoverflow.com/a/71177790/1919821
Here is TypeScript implementation:
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (source === undefined) {
return target;
}
if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach(function(key: string) {
if (isMergebleObject(source[key])) {
if (!target[key]) {
target[key] = {};
}
mergeObjects(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return mergeObjects(target, ...sources);
};
const isObject = (item: any): boolean => {
return item !== null && typeof item === 'object';
};
const isMergebleObject = (item): boolean => {
return isObject(item) && !Array.isArray(item);
};
And Unit Tests:
describe('merge', () => {
it('should merge Objects and all nested Ones', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
});
it('should behave like Object.assign on the top level', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C'};
const obj2 = { a: undefined, b: { b1: 'B1'}};
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
});
it('should not merge array values, just override', () => {
const obj1 = {a: ['A', 'B']};
const obj2 = {a: ['C'], b: ['D']};
expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
});
it('typed merge', () => {
expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
.toEqual(new TestPosition(1, 1));
});
});
class TestPosition {
constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
The deepmerge npm package appears to be the most widely used library for solving this problem:
https://www.npmjs.com/package/deepmerge
I would like to present a pretty simple ES5 alternative. The function gets 2 parameters - target and source that must be of type "object". Target will be the resulting object. Target keeps all its original properties but their values may be modified though.
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
if(prop in target) { // handling merging of two properties with equal names
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else { // two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else { // new properties get added to target
target[prop] = source[prop];
}
}
return target;
}
cases:
if target doesn't have a source property, target gets it;
if target does have a source property and target & source are not
both objects (3 cases out of 4), target's property gets overriden;
if target does have a source property and both of them are objects/arrays (1 remaining case), then recursion happens merging two objects (or concatenation of two arrays);
also consider the following:
array + obj = array
obj + array = obj
obj + obj = obj (recursively merged)
array + array = array (concat)
It is predictable, supports primitive types as well as arrays and objects. Also as we can merge 2 objects, I think that we can merge more than 2 via reduce function.
take a look at an example (and play around with it if you want):
var a = {
"a_prop": 1,
"arr_prop": [4, 5, 6],
"obj": {
"a_prop": {
"t_prop": 'test'
},
"b_prop": 2
}
};
var b = {
"a_prop": 5,
"arr_prop": [7, 8, 9],
"b_prop": 15,
"obj": {
"a_prop": {
"u_prop": false
},
"b_prop": {
"s_prop": null
}
}
};
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false;
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue;
if(prop in target) {
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
console.log(deepMerge(a, b));
There is a limitation - browser's call stack length. Modern browsers will throw an error at some really deep level of recursion (think of thousands of nested calls). Also you are free to treat situations like array + object etc. as you wish by adding new conditions and type checks.
A simple solution with ES5 (overwrite existing value):
function merge(current, update) {
Object.keys(update).forEach(function(key) {
// if update[key] exist, and it's not a string or array,
// we go in one level deeper
if (current.hasOwnProperty(key)
&& typeof current[key] === 'object'
&& !(current[key] instanceof Array)) {
merge(current[key], update[key]);
// if update[key] doesn't exist in current, or it's a string
// or array, then assign/overwrite current[key] to update[key]
} else {
current[key] = update[key];
}
});
return current;
}
var x = { a: { a: 1 } }
var y = { a: { b: 1 } }
console.log(merge(x, y));
Is there a way to do this?
If npm libraries can be used as a solution, object-merge-advanced from yours truly allows to merge objects deeply and customise/override every single merge action using a familiar callback function. The main idea of it is more than just deep merging — what happens with the value when two keys are the same? This library takes care of that — when two keys clash, object-merge-advanced weighs the types, aiming to retain as much data as possible after merging:
First input argument's key is marked #1, second argument's — #2. Depending on each type, one is chosen for the result key's value. In diagram, "an object" means a plain object (not array etc).
When keys don't clash, they all enter the result.
From your example snippet, if you used object-merge-advanced to merge your code snippet:
const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
// a: {
// a: 1,
// b: 1
// }
// }
It's algorithm recursively traverses all input object keys, compares and builds and returns the new merged result.
The following function makes a deep copy of objects, it covers copying primitive, arrays as well as object
function mergeDeep (target, source) {
if (typeof target == "object" && typeof source == "object") {
for (const key in source) {
if (source[key] === null && (target[key] === undefined || target[key] === null)) {
target[key] = null;
} else if (source[key] instanceof Array) {
if (!target[key]) target[key] = [];
//concatenate arrays
target[key] = target[key].concat(source[key]);
} else if (typeof source[key] == "object") {
if (!target[key]) target[key] = {};
this.mergeDeep(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
Most examples here seem too complex, I'm using one in TypeScript I created, I think it should cover most cases (I'm handling arrays as regular data, just replacing them).
const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);
export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
const isDeep = (prop: string) =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...(target as Object),
...(replaced as Object)
} as A & B;
};
Same thing in plain JS, just in case:
const isObject = item => typeof item === 'object' && !Array.isArray(item);
const merge = (target, source) => {
const isDeep = prop =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...target,
...replaced
};
};
Here are my test cases to show how you could use it
describe('merge', () => {
context('shallow merges', () => {
it('merges objects', () => {
const a = { a: 'discard' };
const b = { a: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test' });
});
it('extends objects', () => {
const a = { a: 'test' };
const b = { b: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
});
it('extends a property with an object', () => {
const a = { a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
it('replaces a property with an object', () => {
const a = { b: 'whatever', a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
});
context('deep merges', () => {
it('merges objects', () => {
const a = { test: { a: 'discard', b: 'test' } };
const b = { test: { a: 'test' } } ;
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends objects', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: 'test' } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends a property with an object', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
it('replaces a property with an object', () => {
const a = { test: { b: 'whatever', a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
});
});
Please let me know if you think I'm missing some functionality.
We can use $.extend(true,object1,object2) for deep merging. Value true denotes merge two objects recursively, modifying the first.
$extend(true,target,object)
If your are using ImmutableJS you can use mergeDeep :
fromJS(options).mergeDeep(options2).toJS();
Ramda which is a nice library of javascript functions has mergeDeepLeft and mergeDeepRight. Any of these work pretty well for this problem. Please take a look on the documentation here: https://ramdajs.com/docs/#mergeDeepLeft
For the specific example in question we can use:
import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
with reduce
export const merge = (objFrom, objTo) => Object.keys(objFrom)
.reduce(
(merged, key) => {
merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
? merge(objFrom[key], merged[key] ?? {})
: objFrom[key]
return merged
}, { ...objTo }
)
test('merge', async () => {
const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
const obj3 = merge3(obj1, obj2)
expect(obj3).toEqual(
{ par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
)
})
I was having this issue when loading a cached redux state. If I just load the cached state, I'd run into errors for new app version with an updated state structure.
It was already mentioned, that lodash offers the merge function, which I used:
const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
I didn't like any of the existing solutions. So, I went ahead and wrote my own.
Object.prototype.merge = function(object) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
if (typeof this[key] === "object" && typeof object[key] === "object") {
this[key].merge(object[key]);
continue;
}
this[key] = object[key];
}
}
return this;
}
I hope this helps those of you who struggle to understand what's going on. I've seen a lot of meaningless variables being used on here.
Thanks
Here's another one I just wrote that supports arrays. It concats them.
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function isPlainObject(obj) {
return isObject(obj) && (
obj.constructor === Object // obj = {}
|| obj.constructor === undefined // obj = Object.create(null)
);
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if(Array.isArray(target)) {
if(Array.isArray(source)) {
target.push(...source);
} else {
target.push(source);
}
} else if(isPlainObject(target)) {
if(isPlainObject(source)) {
for(let key of Object.keys(source)) {
if(!target[key]) {
target[key] = source[key];
} else {
mergeDeep(target[key], source[key]);
}
}
} else {
throw new Error(`Cannot merge object with non-object`);
}
} else {
target = source;
}
return mergeDeep(target, ...sources);
};
Use this function:
merge(target, source, mutable = false) {
const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
for (const prop in source) {
if (target[prop] == null || typeof target[prop] === 'undefined') {
newObj[prop] = source[prop];
} else if (Array.isArray(target[prop])) {
newObj[prop] = source[prop] || target[prop];
} else if (target[prop] instanceof RegExp) {
newObj[prop] = source[prop] || target[prop];
} else {
newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
}
}
return newObj;
}
This is a cheap deep merge that uses as little code as I could think of. Each source overwrites the previous property when it exists.
const { keys } = Object;
const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
isObject(a) && isObject(b)
? deepMerge(a, b)
: isObject(a) && !isObject(b)
? a
: b;
const coalesceByKey = source => (acc, key) =>
(acc[key] && source[key]
? (acc[key] = merge(acc[key], source[key]))
: (acc[key] = source[key])) && acc;
/**
* Merge all sources into the target
* overwriting primitive values in the the accumulated target as we go (if they already exist)
* #param {*} target
* #param {...any} sources
*/
const deepMerge = (target, ...sources) =>
sources.reduce(
(acc, source) => keys(source).reduce(coalesceByKey(source), acc),
target
);
console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
for (const prop in source) {
if (!source.hasOwnProperty(prop)) {
continue;
}
if (source[prop] === null) {
// property is null
dest[prop] = source[prop];
continue;
}
if (typeof source[prop] === 'object') {
// if property is object let's dive into in
if (Array.isArray(source[prop])) {
dest[prop] = [];
} else {
if (!dest.hasOwnProperty(prop)
|| typeof dest[prop] !== 'object'
|| dest[prop] === null || Array.isArray(dest[prop])
|| !Object.keys(dest[prop]).length) {
dest[prop] = {};
}
}
recursivelyMoveProperties(source[prop], dest[prop]);
continue;
}
// property is simple type: string, number, e.t.c
dest[prop] = source[prop];
}
return dest;
}
Unit test:
describe('recursivelyMoveProperties', () => {
it('should copy properties correctly', () => {
const source: any = {
propS1: 'str1',
propS2: 'str2',
propN1: 1,
propN2: 2,
propA1: [1, 2, 3],
propA2: [],
propB1: true,
propB2: false,
propU1: null,
propU2: null,
propD1: undefined,
propD2: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subN1: 21,
subN2: 22,
subA1: [21, 22, 23],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
let dest: any = {
propS2: 'str2',
propS3: 'str3',
propN2: -2,
propN3: 3,
propA2: [2, 2],
propA3: [3, 2, 1],
propB2: true,
propB3: false,
propU2: 'not null',
propU3: null,
propD2: 'defined',
propD3: undefined,
propO2: {
subS2: 'inv22',
subS3: 'sub23',
subN2: -22,
subN3: 23,
subA2: [5, 5, 5],
subA3: [31, 32, 33],
subB2: false,
subB3: true,
subU2: 'not null --- ',
subU3: null,
subD2: ' not undefined ----',
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
dest = recursivelyMoveProperties(source, dest);
expect(dest).toEqual({
propS1: 'str1',
propS2: 'str2',
propS3: 'str3',
propN1: 1,
propN2: 2,
propN3: 3,
propA1: [1, 2, 3],
propA2: [],
propA3: [3, 2, 1],
propB1: true,
propB2: false,
propB3: false,
propU1: null,
propU2: null,
propU3: null,
propD1: undefined,
propD2: undefined,
propD3: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subS3: 'sub23',
subN1: 21,
subN2: 22,
subN3: 23,
subA1: [21, 22, 23],
subA2: [],
subA3: [31, 32, 33],
subB1: false,
subB2: true,
subB3: true,
subU1: null,
subU2: null,
subU3: null,
subD1: undefined,
subD2: undefined,
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
});
});
});
Another variation using recursion, hope you find it useful.
const merge = (obj1, obj2) => {
const recursiveMerge = (obj, entries) => {
for (const [key, value] of entries) {
if (typeof value === "object") {
obj[key] = obj[key] ? {...obj[key]} : {};
recursiveMerge(obj[key], Object.entries(value))
else {
obj[key] = value;
}
}
return obj;
}
return recursiveMerge(obj1, Object.entries(obj2))
}
My use case for this was to merge default values into a configuration. If my component accepts a configuration object that has a deeply nested structure, and my component defines a default configuration, I wanted to set default values in my configuration for all configuration options that were not supplied.
Example usage:
export default MyComponent = ({config}) => {
const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
// Component code here
}
This allows me to pass an empty or null config, or a partial config and have all of the values that are not configured fall back to their default values.
My implementation of mergeDefaults looks like this:
export default function mergeDefaults(config, defaults) {
if (config === null || config === undefined) return defaults;
for (var attrname in defaults) {
if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
}
return config;
}
And these are my unit tests
import '#testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';
describe('mergeDefaults', () => {
it('should create configuration', () => {
const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should fill configuration', () => {
const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should not overwrite configuration', () => {
const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('config1');
expect(config.b.d).toStrictEqual('config2');
});
it('should merge configuration', () => {
const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('config2');
expect(config.e).toStrictEqual(15);
});
});
Sometimes you don't need deep merge, even if you think so. For example, if you have a default config with nested objects and you want to extend it deeply with your own config, you can create a class for that. The concept is very simple:
function AjaxConfig(config) {
// Default values + config
Object.assign(this, {
method: 'POST',
contentType: 'text/plain'
}, config);
// Default values in nested objects
this.headers = Object.assign({}, this.headers, {
'X-Requested-With': 'custom'
});
}
// Define your config
var config = {
url: 'https://google.com',
headers: {
'x-client-data': 'CI22yQEI'
}
};
// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);
// View in DevTools
console.log(fullMergedConfig);
You can convert it to a function (not a constructor).
How to deep remove all falsey values and empty objects using lodash ?
f.e. I want my object :
{ a:undefined, b:2, c:4, d:undefined , e:{ f:{} , g:null } }
to become:
{ b:2, c:4 };
var test = {
a: undefined,
b: 2,
c: 4,
d: undefined,
e: {
f: {},
g: null
}
};
function clean(obj) {
for (var propName in obj) {
if (_.isObject(obj[propName])) {
clean(obj[propName]);
}
if (obj[propName] === null || obj[propName] === undefined || _.isObject(obj[propName]) && _.isEmpty(obj[propName])) {
delete obj[propName];
}
}
}
clean(test);
console.log(test);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
You could get the properties to delete and get a clean object. This approach mutates to original object.
function clean(value) {
if (value === null || value === undefined) return true;
if (typeof value !== 'object') return false;
if (Array.isArray(value)) {
let i = value.length;
while (i--) if (clean(value[i])) value.splice(i, 1);
return !value.length;
}
Object
.keys(value)
.filter(k => clean(value[k]))
.forEach(Reflect.deleteProperty.bind(null, value));
return !Object.keys(value).length;
}
const
object = { a: undefined, b: 2, c: 4, d: undefined, e: { f: {}, g: null } , h: [], i: [1, []], j: [[[], []]] };
clean(object);
console.log(object);
The question is pretty vague and doesn't specify whether the original structure may or may not be mutated. There is also nothing in there about arrays.
This answer assumes that you don't want to mutate the original data structure and that "deep" also means to go through all array elements. If the object or array results in an empty instance the corresponding key will be removed.
function deepCompact(collection) {
const add = _.isArray(collection)
? (collection, key, value) => collection.push(value)
: (collection, key, value) => collection[key] = value;
return _.transform(collection, (collection, value, key) => {
if (_.isObject(value)) {
value = deepCompact(value);
if (_.isEmpty(value)) return;
} else {
if (!value) return;
}
add(collection, key, value);
});
}
const data1 = {a: undefined, b: 2, c: 4, d: undefined, e: {f: {} , g: null}};
console.log(deepCompact(data1));
const data2 = [{a: 0, b: 2}, false, {c: [{d: null}]}, {e: [{f: 1}]}];
console.log(deepCompact(data2));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
transform is comparable to reduce, however the first callback parameter is locked to the provided or default accumulator. This means you don't have to return the accumulator from the callback. The default accumulator of transform also differs from reduce. reduce uses the first element as the initial value, whereas transform uses a new object with the same prototype as the provided collection as the default value.
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?
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.
You can use Lodash merge:
var object = {
'a': [{ 'b': 2 }, { 'd': 4 }]
};
var other = {
'a': [{ 'c': 3 }, { 'e': 5 }]
};
console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
The problem is non-trivial when it comes to host objects or any kind of object that's more complex than a bag of values
do you invoke a getter to obtain a value or do you copy over the property descriptor?
what if the merge target has a setter (either own property or in its prototype chain)? Do you consider the value as already-present or call the setter to update the current value?
do you invoke own-property functions or copy them over? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined?
what if it's something like a DOM node? You certainly don't want to treat it as simple object and just deep-merge all its properties over into
how to deal with "simple" structures like arrays or maps or sets? Consider them already-present or merge them too?
how to deal with non-enumerable own properties?
what about new subtrees? Simply assign by reference or deep clone?
how to deal with frozen/sealed/non-extensible objects?
Another thing to keep in mind: Object graphs that contain cycles. It's usually not difficult to deal with - simply keep a Set of already-visited source objects - but often forgotten.
You probably should write a deep-merge function that only expects primitive values and simple objects - at most those types that the structured clone algorithm can handle - as merge sources. Throw if it encounters anything it cannot handle or just assign by reference instead of deep merging.
In other words, there is no one-size-fits-all algorithm, you either have to roll your own or look for a library method that happens to cover your use-cases.
Here is an immutable (does not modify the inputs) version of #Salakar's answer. Useful if you're doing functional programming type stuff.
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
export default function mergeDeep(target, source) {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
Update 2022:
I created mergician to address the various merge/clone requirements discussed in the comments. It is based on the same concept as my original answer (below) but offers configurable options:
Unlike native methods and other merge/clone utilities, Mergician provides advanced options for customizing the merge/clone process. These options make it easy to inspect, filter, and modify keys and properties; merge or skip unique, common, and universal keys (i.e., intersections, unions, and differences); and merge, sort, and remove duplicates from arrays. Property accessors and descriptors are also handled properly, ensuring that getter/setter functions are retained and descriptor values are defined on new merged/cloned objects.
Notably, mergician is significantly smaller (1.5k min+gzip) than similar utilities like lodash.merge (5.1k min+gzip).
GitHub: https://github.com/jhildenbiddle/mergician
NPM: https://www.npmjs.com/package/mergician
Docs: https://jhildenbiddle.github.io/mergician/
Original answer:
Since this issue is still active, here's another approach:
ES6/2015
Immutable (does not modify original objects)
Handles arrays (concatenates them)
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* #param {...object} objects - Objects to merge
* #returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
// Test objects
const obj1 = {
a: 1,
b: 1,
c: { x: 1, y: 1 },
d: [ 1, 1 ]
}
const obj2 = {
b: 2,
c: { y: 2, z: 2 },
d: [ 2, 2 ],
e: 2
}
const obj3 = mergeDeep(obj1, obj2);
// Out
console.log(obj3);
I know there's a lot of answers already and as many comments arguing they won't work. The only consensus is that it's so complicated that nobody made a standard for it. However, most of accepted answers in SO expose "simple tricks" that are widely used. So, for all of us like me who are no experts but want to write safer code by grasping a little more about javascript's complexity, I'll try to shed some light.
Before getting our hands dirty, let me clarify 2 points:
[DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either.
I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.
Answers with for..in or Object.keys are misleading
Making a deep copy seems so basic and common practice that we expect to find a one-liner or, at least, a quick win via simple recursion. We don't expect we should need a library or write a custom function of 100 lines.
When I first read Salakar's answer, I genuinely thought I could do better and simpler (you can compare it with Object.assign on x={a:1}, y={a:{b:1}}). Then I read the8472's answer and I thought... there is no getting away so easily, improving already given answers won't get us far.
Let's let deep copy and recursive aside an instant. Just consider how (wrongly) people parse properties to copy a very simple object.
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Object.keys will omit own non-enumerable properties, own symbol-keyed properties and all prototype's properties. It may be fine if your objects don't have any of those. But keep it mind that Object.assign handles own symbol-keyed enumerable properties. So your custom copy lost its bloom.
for..in will provide properties of the source, of its prototype and of the full prototype chain without you wanting it (or knowing it). Your target may end up with too many properties, mixing up prototype properties and own properties.
If you're writing a general purpose function and you're not using Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbols or Object.getPrototypeOf, you're most probably doing it wrong.
Things to consider before writing your function
First, make sure you understand what a Javascript object is. In Javascript, an object is made of its own properties and a (parent) prototype object. The prototype object in turn is made of its own properties and a prototype object. And so on, defining a prototype chain.
A property is a pair of key (string or symbol) and descriptor (value or get/set accessor, and attributes like enumerable).
Finally, there are many types of objects. You may want to handle differently an object Object from an object Date or an object Function.
So, writing your deep copy, you should answer at least those questions:
What do I consider deep (proper for recursive look up) or flat?
What properties do I want to copy? (enumerable/non-enumerable, string-keyed/symbol-keyed, own properties/prototype's own properties, values/descriptors...)
For my example, I consider that only the object Objects are deep, because other objects created by other constructors may not be proper for an in-depth look. Customized from this SO.
function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
And I made an options object to choose what to copy (for demo purpose).
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Proposed function
You can test it in this plunker.
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
That can be used like this:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
If you want to have a one liner without requiring a huge library like lodash, I suggest you to use deepmerge (npm install deepmerge) or deepmerge-ts (npm install deepmerge-ts).
deepmerge also comes with typings for TypeScript and is more stable (since it's older), but deepmerge-ts is also available for Deno and is faster by design, although written in TypeScript as the name implies.
Once imported you can do
deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });
to get
{ a: 2, b: 2, c: 3, d: 3 }
This works nicely with complex objects and arrays. A real all-rounder solution this is.
Many answers use tens of lines of code, or require adding a new library to the project, but if you use recursion, this is just 4 lines of code.
function merge(current, updates) {
for (key of Object.keys(updates)) {
if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
else merge(current[key], updates[key]);
}
return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));
Arrays handling: The above version overwrites old array values with new ones. If you want it to keep the old array values and add the new ones, just add a else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key]) block above the else statament and you're all set.
I use lodash:
import _ = require('lodash');
value = _.merge(value1, value2);
Here, straight forward;
a simple solution that works like Object.assign just deep, and works for an array, without any modification.
function deepAssign(target, ...sources) {
for (source of sources) {
for (let k in source) {
let vs = source[k], vt = target[k]
if (Object(vs) == vs && Object(vt) === vt) {
target[k] = deepAssign(vt, vs)
continue
}
target[k] = source[k]
}
}
return target
}
x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)
console.log(JSON.stringify(x) === JSON.stringify({
"a": {
"a": 1,
"b": 1
},
"b": [ 1, 2, null, 4 ],
"c": 3
}))
Edit:
I answer somewhere else about a new method for deep comparing 2 objects.
that method can use also for a deep merging. If you want implantation put a comment
https://stackoverflow.com/a/71177790/1919821
Here is TypeScript implementation:
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (source === undefined) {
return target;
}
if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach(function(key: string) {
if (isMergebleObject(source[key])) {
if (!target[key]) {
target[key] = {};
}
mergeObjects(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return mergeObjects(target, ...sources);
};
const isObject = (item: any): boolean => {
return item !== null && typeof item === 'object';
};
const isMergebleObject = (item): boolean => {
return isObject(item) && !Array.isArray(item);
};
And Unit Tests:
describe('merge', () => {
it('should merge Objects and all nested Ones', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
});
it('should behave like Object.assign on the top level', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C'};
const obj2 = { a: undefined, b: { b1: 'B1'}};
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
});
it('should not merge array values, just override', () => {
const obj1 = {a: ['A', 'B']};
const obj2 = {a: ['C'], b: ['D']};
expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
});
it('typed merge', () => {
expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
.toEqual(new TestPosition(1, 1));
});
});
class TestPosition {
constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
The deepmerge npm package appears to be the most widely used library for solving this problem:
https://www.npmjs.com/package/deepmerge
I would like to present a pretty simple ES5 alternative. The function gets 2 parameters - target and source that must be of type "object". Target will be the resulting object. Target keeps all its original properties but their values may be modified though.
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
if(prop in target) { // handling merging of two properties with equal names
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else { // two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else { // new properties get added to target
target[prop] = source[prop];
}
}
return target;
}
cases:
if target doesn't have a source property, target gets it;
if target does have a source property and target & source are not
both objects (3 cases out of 4), target's property gets overriden;
if target does have a source property and both of them are objects/arrays (1 remaining case), then recursion happens merging two objects (or concatenation of two arrays);
also consider the following:
array + obj = array
obj + array = obj
obj + obj = obj (recursively merged)
array + array = array (concat)
It is predictable, supports primitive types as well as arrays and objects. Also as we can merge 2 objects, I think that we can merge more than 2 via reduce function.
take a look at an example (and play around with it if you want):
var a = {
"a_prop": 1,
"arr_prop": [4, 5, 6],
"obj": {
"a_prop": {
"t_prop": 'test'
},
"b_prop": 2
}
};
var b = {
"a_prop": 5,
"arr_prop": [7, 8, 9],
"b_prop": 15,
"obj": {
"a_prop": {
"u_prop": false
},
"b_prop": {
"s_prop": null
}
}
};
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false;
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue;
if(prop in target) {
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
console.log(deepMerge(a, b));
There is a limitation - browser's call stack length. Modern browsers will throw an error at some really deep level of recursion (think of thousands of nested calls). Also you are free to treat situations like array + object etc. as you wish by adding new conditions and type checks.
A simple solution with ES5 (overwrite existing value):
function merge(current, update) {
Object.keys(update).forEach(function(key) {
// if update[key] exist, and it's not a string or array,
// we go in one level deeper
if (current.hasOwnProperty(key)
&& typeof current[key] === 'object'
&& !(current[key] instanceof Array)) {
merge(current[key], update[key]);
// if update[key] doesn't exist in current, or it's a string
// or array, then assign/overwrite current[key] to update[key]
} else {
current[key] = update[key];
}
});
return current;
}
var x = { a: { a: 1 } }
var y = { a: { b: 1 } }
console.log(merge(x, y));
Is there a way to do this?
If npm libraries can be used as a solution, object-merge-advanced from yours truly allows to merge objects deeply and customise/override every single merge action using a familiar callback function. The main idea of it is more than just deep merging — what happens with the value when two keys are the same? This library takes care of that — when two keys clash, object-merge-advanced weighs the types, aiming to retain as much data as possible after merging:
First input argument's key is marked #1, second argument's — #2. Depending on each type, one is chosen for the result key's value. In diagram, "an object" means a plain object (not array etc).
When keys don't clash, they all enter the result.
From your example snippet, if you used object-merge-advanced to merge your code snippet:
const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
// a: {
// a: 1,
// b: 1
// }
// }
It's algorithm recursively traverses all input object keys, compares and builds and returns the new merged result.
The following function makes a deep copy of objects, it covers copying primitive, arrays as well as object
function mergeDeep (target, source) {
if (typeof target == "object" && typeof source == "object") {
for (const key in source) {
if (source[key] === null && (target[key] === undefined || target[key] === null)) {
target[key] = null;
} else if (source[key] instanceof Array) {
if (!target[key]) target[key] = [];
//concatenate arrays
target[key] = target[key].concat(source[key]);
} else if (typeof source[key] == "object") {
if (!target[key]) target[key] = {};
this.mergeDeep(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
Most examples here seem too complex, I'm using one in TypeScript I created, I think it should cover most cases (I'm handling arrays as regular data, just replacing them).
const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);
export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
const isDeep = (prop: string) =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...(target as Object),
...(replaced as Object)
} as A & B;
};
Same thing in plain JS, just in case:
const isObject = item => typeof item === 'object' && !Array.isArray(item);
const merge = (target, source) => {
const isDeep = prop =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...target,
...replaced
};
};
Here are my test cases to show how you could use it
describe('merge', () => {
context('shallow merges', () => {
it('merges objects', () => {
const a = { a: 'discard' };
const b = { a: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test' });
});
it('extends objects', () => {
const a = { a: 'test' };
const b = { b: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
});
it('extends a property with an object', () => {
const a = { a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
it('replaces a property with an object', () => {
const a = { b: 'whatever', a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
});
context('deep merges', () => {
it('merges objects', () => {
const a = { test: { a: 'discard', b: 'test' } };
const b = { test: { a: 'test' } } ;
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends objects', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: 'test' } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends a property with an object', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
it('replaces a property with an object', () => {
const a = { test: { b: 'whatever', a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
});
});
Please let me know if you think I'm missing some functionality.
We can use $.extend(true,object1,object2) for deep merging. Value true denotes merge two objects recursively, modifying the first.
$extend(true,target,object)
If your are using ImmutableJS you can use mergeDeep :
fromJS(options).mergeDeep(options2).toJS();
Ramda which is a nice library of javascript functions has mergeDeepLeft and mergeDeepRight. Any of these work pretty well for this problem. Please take a look on the documentation here: https://ramdajs.com/docs/#mergeDeepLeft
For the specific example in question we can use:
import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
with reduce
export const merge = (objFrom, objTo) => Object.keys(objFrom)
.reduce(
(merged, key) => {
merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
? merge(objFrom[key], merged[key] ?? {})
: objFrom[key]
return merged
}, { ...objTo }
)
test('merge', async () => {
const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
const obj3 = merge3(obj1, obj2)
expect(obj3).toEqual(
{ par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
)
})
I was having this issue when loading a cached redux state. If I just load the cached state, I'd run into errors for new app version with an updated state structure.
It was already mentioned, that lodash offers the merge function, which I used:
const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
I didn't like any of the existing solutions. So, I went ahead and wrote my own.
Object.prototype.merge = function(object) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
if (typeof this[key] === "object" && typeof object[key] === "object") {
this[key].merge(object[key]);
continue;
}
this[key] = object[key];
}
}
return this;
}
I hope this helps those of you who struggle to understand what's going on. I've seen a lot of meaningless variables being used on here.
Thanks
Here's another one I just wrote that supports arrays. It concats them.
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function isPlainObject(obj) {
return isObject(obj) && (
obj.constructor === Object // obj = {}
|| obj.constructor === undefined // obj = Object.create(null)
);
}
function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if(Array.isArray(target)) {
if(Array.isArray(source)) {
target.push(...source);
} else {
target.push(source);
}
} else if(isPlainObject(target)) {
if(isPlainObject(source)) {
for(let key of Object.keys(source)) {
if(!target[key]) {
target[key] = source[key];
} else {
mergeDeep(target[key], source[key]);
}
}
} else {
throw new Error(`Cannot merge object with non-object`);
}
} else {
target = source;
}
return mergeDeep(target, ...sources);
};
Use this function:
merge(target, source, mutable = false) {
const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
for (const prop in source) {
if (target[prop] == null || typeof target[prop] === 'undefined') {
newObj[prop] = source[prop];
} else if (Array.isArray(target[prop])) {
newObj[prop] = source[prop] || target[prop];
} else if (target[prop] instanceof RegExp) {
newObj[prop] = source[prop] || target[prop];
} else {
newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
}
}
return newObj;
}
This is a cheap deep merge that uses as little code as I could think of. Each source overwrites the previous property when it exists.
const { keys } = Object;
const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
isObject(a) && isObject(b)
? deepMerge(a, b)
: isObject(a) && !isObject(b)
? a
: b;
const coalesceByKey = source => (acc, key) =>
(acc[key] && source[key]
? (acc[key] = merge(acc[key], source[key]))
: (acc[key] = source[key])) && acc;
/**
* Merge all sources into the target
* overwriting primitive values in the the accumulated target as we go (if they already exist)
* #param {*} target
* #param {...any} sources
*/
const deepMerge = (target, ...sources) =>
sources.reduce(
(acc, source) => keys(source).reduce(coalesceByKey(source), acc),
target
);
console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
for (const prop in source) {
if (!source.hasOwnProperty(prop)) {
continue;
}
if (source[prop] === null) {
// property is null
dest[prop] = source[prop];
continue;
}
if (typeof source[prop] === 'object') {
// if property is object let's dive into in
if (Array.isArray(source[prop])) {
dest[prop] = [];
} else {
if (!dest.hasOwnProperty(prop)
|| typeof dest[prop] !== 'object'
|| dest[prop] === null || Array.isArray(dest[prop])
|| !Object.keys(dest[prop]).length) {
dest[prop] = {};
}
}
recursivelyMoveProperties(source[prop], dest[prop]);
continue;
}
// property is simple type: string, number, e.t.c
dest[prop] = source[prop];
}
return dest;
}
Unit test:
describe('recursivelyMoveProperties', () => {
it('should copy properties correctly', () => {
const source: any = {
propS1: 'str1',
propS2: 'str2',
propN1: 1,
propN2: 2,
propA1: [1, 2, 3],
propA2: [],
propB1: true,
propB2: false,
propU1: null,
propU2: null,
propD1: undefined,
propD2: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subN1: 21,
subN2: 22,
subA1: [21, 22, 23],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
let dest: any = {
propS2: 'str2',
propS3: 'str3',
propN2: -2,
propN3: 3,
propA2: [2, 2],
propA3: [3, 2, 1],
propB2: true,
propB3: false,
propU2: 'not null',
propU3: null,
propD2: 'defined',
propD3: undefined,
propO2: {
subS2: 'inv22',
subS3: 'sub23',
subN2: -22,
subN3: 23,
subA2: [5, 5, 5],
subA3: [31, 32, 33],
subB2: false,
subB3: true,
subU2: 'not null --- ',
subU3: null,
subD2: ' not undefined ----',
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
};
dest = recursivelyMoveProperties(source, dest);
expect(dest).toEqual({
propS1: 'str1',
propS2: 'str2',
propS3: 'str3',
propN1: 1,
propN2: 2,
propN3: 3,
propA1: [1, 2, 3],
propA2: [],
propA3: [3, 2, 1],
propB1: true,
propB2: false,
propB3: false,
propU1: null,
propU2: null,
propU3: null,
propD1: undefined,
propD2: undefined,
propD3: undefined,
propO1: {
subS1: 'sub11',
subS2: 'sub12',
subN1: 11,
subN2: 12,
subA1: [11, 12, 13],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
propO2: {
subS1: 'sub21',
subS2: 'sub22',
subS3: 'sub23',
subN1: 21,
subN2: 22,
subN3: 23,
subA1: [21, 22, 23],
subA2: [],
subA3: [31, 32, 33],
subB1: false,
subB2: true,
subB3: true,
subU1: null,
subU2: null,
subU3: null,
subD1: undefined,
subD2: undefined,
subD3: undefined,
},
propO3: {
subS1: 'sub31',
subS2: 'sub32',
subN1: 31,
subN2: 32,
subA1: [31, 32, 33],
subA2: [],
subB1: false,
subB2: true,
subU1: null,
subU2: null,
subD1: undefined,
subD2: undefined,
},
});
});
});
Another variation using recursion, hope you find it useful.
const merge = (obj1, obj2) => {
const recursiveMerge = (obj, entries) => {
for (const [key, value] of entries) {
if (typeof value === "object") {
obj[key] = obj[key] ? {...obj[key]} : {};
recursiveMerge(obj[key], Object.entries(value))
else {
obj[key] = value;
}
}
return obj;
}
return recursiveMerge(obj1, Object.entries(obj2))
}
My use case for this was to merge default values into a configuration. If my component accepts a configuration object that has a deeply nested structure, and my component defines a default configuration, I wanted to set default values in my configuration for all configuration options that were not supplied.
Example usage:
export default MyComponent = ({config}) => {
const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
// Component code here
}
This allows me to pass an empty or null config, or a partial config and have all of the values that are not configured fall back to their default values.
My implementation of mergeDefaults looks like this:
export default function mergeDefaults(config, defaults) {
if (config === null || config === undefined) return defaults;
for (var attrname in defaults) {
if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
}
return config;
}
And these are my unit tests
import '#testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';
describe('mergeDefaults', () => {
it('should create configuration', () => {
const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should fill configuration', () => {
const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(10);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('default2');
});
it('should not overwrite configuration', () => {
const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('config1');
expect(config.b.d).toStrictEqual('config2');
});
it('should merge configuration', () => {
const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
expect(config.a).toStrictEqual(12);
expect(config.b.c).toStrictEqual('default1');
expect(config.b.d).toStrictEqual('config2');
expect(config.e).toStrictEqual(15);
});
});
Sometimes you don't need deep merge, even if you think so. For example, if you have a default config with nested objects and you want to extend it deeply with your own config, you can create a class for that. The concept is very simple:
function AjaxConfig(config) {
// Default values + config
Object.assign(this, {
method: 'POST',
contentType: 'text/plain'
}, config);
// Default values in nested objects
this.headers = Object.assign({}, this.headers, {
'X-Requested-With': 'custom'
});
}
// Define your config
var config = {
url: 'https://google.com',
headers: {
'x-client-data': 'CI22yQEI'
}
};
// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);
// View in DevTools
console.log(fullMergedConfig);
You can convert it to a function (not a constructor).