How to remove undefined and null values from an object using lodash? - javascript

I have a Javascript object like:
var my_object = { a:undefined, b:2, c:4, d:undefined };
How to remove all the undefined properties? False attributes should stay.

You can simply chain _.omit() with _.isUndefined and _.isNull compositions, and get the result with lazy evaluation.
Demo
var result = _(my_object).omit(_.isUndefined).omit(_.isNull).value();
Update March 14, 2016:
As mentioned by dylants in the comment section, you should use the _.omitBy() function since it uses a predicate instead of a property. You should use this for lodash version 4.0.0 and above.
DEMO
var result = _(my_object).omitBy(_.isUndefined).omitBy(_.isNull).value();
Update June 1, 2016:
As commented by Max Truxa, lodash already provided an alternative _.isNil, which checks for both null and undefined:
var result = _.omitBy(my_object, _.isNil);

If you want to remove all falsey values then the most compact way is:
For Lodash 4.x and later:
_.pickBy({ a: null, b: 1, c: undefined }, _.identity);
>> Object {b: 1}
For legacy Lodash 3.x:
_.pick(obj, _.identity);
_.pick({ a: null, b: 1, c: undefined }, _.identity);
>> Object {b: 1}

The correct answer is:
_.omitBy({ a: null, b: 1, c: undefined, d: false }, _.isNil)
That results in:
{b: 1, d: false}
The alternative given here by other people:
_.pickBy({ a: null, b: 1, c: undefined, d: false }, _.identity);
Will remove also false values which is not desired here.

if you are using lodash, you can use _.compact(array) to remove all falsely values from an array.
_.compact([0, 1, false, 2, '', 3]);
// => [1, 2, 3]
https://lodash.com/docs/4.17.4#compact

Just:
_.omit(my_object, _.isUndefined)
The above doesn't take in account null values, as they are missing from the original example and mentioned only in the subject, but I leave it as it is elegant and might have its uses.
Here is the complete example, less concise, but more complete.
var obj = { a: undefined, b: 2, c: 4, d: undefined, e: null, f: false, g: '', h: 0 };
console.log(_.omit(obj, function(v) { return _.isUndefined(v) || _.isNull(v); }));

To complete the other answers, in lodash 4 to ignore only undefined and null (And not properties like false) you can use a predicate in _.pickBy:
_.pickBy(obj, v !== null && v !== undefined)
Example below :
const obj = { a: undefined, b: 123, c: true, d: false, e: null};
const filteredObject = _.pickBy(obj, v => v !== null && v !== undefined);
console.log = (obj) => document.write(JSON.stringify(filteredObject, null, 2));
console.log(filteredObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>

According to lodash docs:
_.compact(_.map(array, fn))
Also you can filter out all nulls

For deep nested object you can use my snippet for lodash > 4
const removeObjectsWithNull = (obj) => {
return _(obj)
.pickBy(_.isObject) // get only objects
.mapValues(removeObjectsWithNull) // call only for values as objects
.assign(_.omitBy(obj, _.isObject)) // save back result that is not object
.omitBy(_.isNil) // remove null and undefined from object
.value(); // get value
};

with pure JavaScript: (although Object.entries is ES7, Object.assign is ES6; but equivalent ES5 uses Object.keys only should be also doable); also notice v != null checks for both null and undefined;
> var d = { a:undefined, b:2, c:0, d:undefined, e: null, f: 0.3, s: "", t: false };
undefined
> Object.entries(d)
.filter(([ k, v ]) => (v != null))
.reduce((acc, [k, v]) => Object.assign(acc, {[k]: v}), {})
{ b: 2, c: 0, f: 0.3, s: '', t: false }
Edit: this below is the version with ES5 Object.keys only: but generally with ES7 in Node v8 is pretty much enjoyable ;-)
> Object.keys(d)
.filter(function(k) { return d[k] != null; })
.reduce(function(acc, k) { acc[k] = d[k]; return acc; }, {});
{ b: 2, c: 0, f: 0.3, s: '', t: false }
Update in October 2017: with Node v8 (since v8.3 or so) now it has object spreading construct:
> var d = { a:undefined, b:2, c:0, d:undefined,
e: null, f: -0.0, s: "", t: false, inf: +Infinity, nan: NaN };
undefined
> Object.entries(d)
.filter(([ k, v ]) => (v != null))
.reduce((acc, [k, v]) => ({...acc, [k]: v}), {})
{ b: 2, c: 0, f: -0, s: '', t: false, inf: Infinity, nan: NaN }
or within one reduce only:
> Object.entries(d)
.reduce((acc, [k, v]) => (v==null ? acc : {...acc, [k]: v}), {})
{ b: 2, c: 0, f: -0, s: '', t: false, inf: Infinity, nan: NaN }
Update: someone want recursive? isn't that hard either, just need an additional check of isObject, and recursively call itself:
> function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]"; }
undefined
> function dropNullUndefined(d) {
return Object.entries(d)
.reduce((acc, [k, v]) => (
v == null ? acc :
{...acc, [k]: (isObject(v) ? dropNullUndefined(v) : v) }
), {});
}
> dropNullUndefined({a: 3, b:null})
{ a: 3 }
> dropNullUndefined({a: 3, b:null, c: { d: 0, e: undefined }})
{ a: 3, c: { d: 0 } }
my conclusion: if pure Javascript can do, I would avoid any third party library dependencies:

To remove undefined, null, and empty string from object
_.omitBy(object, (v) => _.isUndefined(v) || _.isNull(v) || v === '');

I encountered a similar problem with removing undefined from an object (deeply), and found that if you are OK to convert your plain old object and use JSON, a quick and dirty helper function would look like this:
function stripUndefined(obj) {
return JSON.parse(JSON.stringify(obj));
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description
"...If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array)."

Since some of you might have arrived at the question looking to specifically removing only undefined, you can use:
a combination of Lodash methods
_.omitBy(object, _.isUndefined)
the rundef package, which removes only undefined properties
rundef(object)
If you need to recursively remove undefined properties, the rundef package also has a recursive option.
rundef(object, false, true);
See the documentation for more details.

Here's the lodash approach I'd take:
_(my_object)
.pairs()
.reject(function(item) {
return _.isUndefined(item[1]) ||
_.isNull(item[1]);
})
.zipObject()
.value()
The pairs() function turns the input object into an array of key/value arrays. You do this so that it's easier to use reject() to eliminate undefined and null values. After, you're left with pairs that weren't rejected, and these are input for zipObject(), which reconstructs your object for you.

Taking in account that undefined == null we can write as follows:
let collection = {
a: undefined,
b: 2,
c: 4,
d: null,
}
console.log(_.omit(collection, it => it == null))
// -> { b: 2, c: 4 }
JSBin example

I like using _.pickBy, because you have full control over what you are removing:
var person = {"name":"bill","age":21,"sex":undefined,"height":null};
var cleanPerson = _.pickBy(person, function(value, key) {
return !(value === undefined || value === null);
});
Source: https://www.codegrepper.com/?search_term=lodash+remove+undefined+values+from+object

You can also use Object.entries with Array.prototype.filter.
const omitNullish = (object) =>
Object.fromEntries(
Object.entries(object).filter(([, value]) => value != null)
)
omitNullish({ a: null, b: 1, c: undefined, d: false, e: 0 }) // { b: 1, d: false, e: 0}
If you want to use lodash, they are removing omit from v5 so an alternative is to use fp/pickBy along with isNil and negate.
import pickBy from 'lodash/fp/pickBy'
import isNil from 'lodash/isNil';
import negate from 'lodash/negate';
const omitNullish = pickBy(negate(isNil))
omitNullish({ a: null, b: 1, c: undefined, d: false, e: 0 }) // { b: 1, d: false, e: 0}

pickBy uses identity by default:
_.pickBy({ a: null, b: 1, c: undefined, d: false });

With lodash (or underscore) You may do
var my_object = { a:undefined, b:2, c:4, d:undefined, e:null };
var passedKeys = _.reject(Object.keys(my_object), function(key){ return _.isUndefined(my_object[key]) || _.isNull(my_object[key]) })
newObject = {};
_.each(passedKeys, function(key){
newObject[key] = my_object[key];
});
Otherwise, with vanilla JavaScript, you can do
var my_object = { a:undefined, b:2, c:4, d:undefined };
var new_object = {};
Object.keys(my_object).forEach(function(key){
if (typeof my_object[key] != 'undefined' && my_object[key]!=null){
new_object[key] = my_object[key];
}
});
Not to use a falsey test, because not only "undefined" or "null" will be rejected, also is other falsey value like "false", "0", empty string, {}. Thus, just to make it simple and understandable, I opted to use explicit comparison as coded above.

To omit all falsey values but keep the boolean primitives this solution helps.
_.omitBy(fields, v => (_.isBoolean(v)||_.isFinite(v)) ? false : _.isEmpty(v));
let fields = {
str: 'CAD',
numberStr: '123',
number : 123,
boolStrT: 'true',
boolStrF: 'false',
boolFalse : false,
boolTrue : true,
undef: undefined,
nul: null,
emptyStr: '',
array: [1,2,3],
emptyArr: []
};
let nobj = _.omitBy(fields, v => (_.isBoolean(v)||_.isFinite(v)) ? false : _.isEmpty(v));
console.log(nobj);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>

Shortest way (lodash v4):
_.pickBy(my_object)

If you don't want to remove false values. Here is an example:
obj = {
"a": null,
"c": undefined,
"d": "a",
"e": false,
"f": true
}
_.pickBy(obj, x => x === false || x)
> {
"d": "a",
"e": false,
"f": true
}

You can use lodash to remove null and undefined objects , but you should konw what lodash method you need to use, many dev uses isNil to remove the Null and undefined objects , but this function not remove the empty objects (' ')
you can use isEmpty to remove Null , Undefined and
import pickBy from 'lodash/fp/pickBy'
import negate from 'lodash/negate';
import isEmpty from 'lodash/isEmpty';
const omitNullish = pickBy(negate(isEmpty));
addressObject = {
"a": null,
"c": undefined,
"d": "",
"e": "test1",
"f": "test2
}
const notNullObjects = omitNullish(addressObject);
console.log(notNullObjects);
you will have this object : {
"e": "test1",
"f": "test2
}

var my_object = { a:undefined, b:2, c:4, d:undefined };
var newObject = _.reject(my_collection, function(val){ return _.isUndefined(val) })
//--> newCollection = { b: 2, c: 4 }

I would use underscore and take care of empty strings too:
var my_object = { a:undefined, b:2, c:4, d:undefined, k: null, p: false, s: '', z: 0 };
var result =_.omit(my_object, function(value) {
return _.isUndefined(value) || _.isNull(value) || value === '';
});
console.log(result); //Object {b: 2, c: 4, p: false, z: 0}
jsbin.

For deep nested object and arrays. and exclude empty values from string and NaN
function isBlank(value) {
return _.isEmpty(value) && !_.isNumber(value) || _.isNaN(value);
}
var removeObjectsWithNull = (obj) => {
return _(obj).pickBy(_.isObject)
.mapValues(removeObjectsWithNull)
.assign(_.omitBy(obj, _.isObject))
.assign(_.omitBy(obj, _.isArray))
.omitBy(_.isNil).omitBy(isBlank)
.value();
}
var obj = {
teste: undefined,
nullV: null,
x: 10,
name: 'Maria Sophia Moura',
a: null,
b: '',
c: {
a: [{
n: 'Gleidson',
i: 248
}, {
t: 'Marta'
}],
g: 'Teste',
eager: {
p: 'Palavra'
}
}
}
removeObjectsWithNull(obj)
result:
{
"c": {
"a": [
{
"n": "Gleidson",
"i": 248
},
{
"t": "Marta"
}
],
"g": "Teste",
"eager": {
"p": "Palavra"
}
},
"x": 10,
"name": "Maria Sophia Moura"
}

For those of you getting here looking to remove from an array of objects and using lodash you can do something like this:
const objects = [{ a: 'string', b: false, c: 'string', d: undefined }]
const result = objects.map(({ a, b, c, d }) => _.pickBy({ a,b,c,d }, _.identity))
// [{ a: 'string', c: 'string' }]
Note: You don't have to destruct if you don't want to.

I was able to do this in deep objects that include arrays with just one lodash function, transform.
Note that the double-unequal (!= null) is intentional as it will also match undefined, as is the typeof 'object' check as it will match both object and array.
This is for use with plain data objects only that don't contain classes.
const cloneDeepSanitized = (obj) =>
Array.isArray(obj)
? obj.filter((entry) => entry != null).map(cloneDeepSanitized)
: transform(
obj,
(result, val, key) => {
if (val != null) {
result[key] =
typeof val === 'object' ? cloneDeepSanitized(val) : val;
}
},
{},
);

Related

How to find difference between two Object and get those changes merge in single Object in JavaScript? [duplicate]

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).

Javascript Object get properties from lowest level

I want to know whats the best way to simplify a object so that it only contains the properties from the lowest level of the actual object. I will explain by the following example:
Starting Object
const start = {
a: 1,
b: {
c: 2,
d: 3
},
e: 4,
f:{
g: 5
}
}
Wanted Object
const wanted = {
a: 1,
c: 2,
d: 3,
e: 4,
g: 5
}
I generally know how to do it, but it seems very lavish for large objects. Thus, I do not want to write:
const simplified = {
a: start.a,
c: start.b.c,
d: start.b.d,
e: start.e,
g: start.f.g
}
Apparently, destructuring an object inside another object is not possible, but it would also help if I can get the children of the parent objects easier, somehow like that:
const simplified = {
a: start.a,
//start.b.children?
e: start.e,
//start.f.children?
}
I also want to avoid to create duplicates of the variables outside the object creation with destructuring.
What is the best practice for restructuring an object in such way?
You can simply create a recursive function that will add property to your result only if the current value is not of the object type, and if it is then pass it to another recursive call.
const start = {"a":1,"b":{"c":2,"d":3},"e":4,"f":{"g":5}}
function flatten(data) {
return Object.entries(data).reduce((r, [k, v]) => {
if (typeof v === 'object') Object.assign(r, flatten(v))
else r[k] = v
return r
}, {})
}
const result = flatten(start)
console.log(result)
You could map either the object or the nested parts.
function flat(object) {
return Object.assign({}, ...Object
.entries(object)
.map(([k, v]) => v && typeof v === 'object'
? flat(v)
: { [k]: v }
));
}
console.log(flat({ a: 1, b: { c: 2, d: 3 }, e: 4, f:{ g: 5 } }));

Over-ride multi-dimensional array [duplicate]

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 falsey values and empty objects from an object using lodash

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.

ES6/7 Objects Assign Deep [duplicate]

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).

Categories

Resources