C
codeguru
Member
- Freecoin
- 287
I recently shared how you can merge object properties with the spread operator but this method has one big limitation: the spread operator merge isn’t a “deep” merge, meaning merges are recursive. Moreover nested object properties aren’t merged — the last value specified in the merge replaces the last, even when there are other properties that should exist.
const defaultPerson = {
name: 'Anon',
gender: 'Female',
hair: {
color: 'brown',
cut: 'long'
},
eyes: 'blue',
family: ['mom', 'dad']
};
const me = {
name: 'David Walsh',
gender: 'Male',
hair: {
cut: 'short'
},
family: ['wife', 'kids', 'dog']
};
const summary = {...defaultPerson, ...me};
/*
{
"name":"David Walsh",
"gender":"Male",
"hair":{
"cut":"short"
},
"eyes":"blue",
"family":[
"wife",
"kids",
"dog"
]
}
*/
In the sample above, you’ll notice that the
Deep merging in JavaScript is important, especially with the common practice of “default” or “options” objects with many properties and nested objects that often get merged with instance-specific values. If you’re looking for a utility to help with deep merges, look no further than the tiny deepmerge utility!
When you use the
const deepmerge = require('deepmerge');
// ...
const summary = deepmerge(defaultPerson, me);
/*
{
"name":"David Walsh",
"gender":"Male",
"hair":{
"color":"brown",
"cut":"short"
},
"eyes":"blue",
"family":[
"mom",
"dad",
"wife",
"kids",
"dog"
]
}
*/
const result = deepmerge.all([,
{ level1: { level2: { name: 'David', parts: ['head', 'shoulders'] } } },
{ level1: { level2: { face: 'meh', parts: ['knees', 'toes'] } } },
{ level1: { level2: { eyes: 'more meh', parts: ['eyes'] } } },
]);
/*
{
"level1":{
"level2":{
"name":"David",
"parts":[
"head",
"shoulders",
"knees",
"toes",
"eyes"
],
"face":"meh",
"eyes":"more meh"
}
}
}
*/
function isMergeableObject(val) {
var nonNullObject = val && typeof val === 'object'
return nonNullObject
&& Object.prototype.toString.call(val) !== '[object RegExp]'
&& Object.prototype.toString.call(val) !== '[object Date]'
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true
return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
}
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice()
source.forEach(function(e, i) {
if (typeof destination === 'undefined') {
destination = cloneIfNecessary(e, optionsArgument)
} else if (isMergeableObject(e)) {
destination = deepmerge(target, e, optionsArgument)
} else if (target.indexOf(e) === -1) {
destination.push(cloneIfNecessary(e, optionsArgument))
}
})
return destination
}
function mergeObject(target, source, optionsArgument) {
var destination = {}
if (isMergeableObject(target)) {
Object.keys(target).forEach(function (key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument)
})
}
Object.keys(source).forEach(function (key) {
if (!isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument)
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument)
}
})
return destination
}
function deepmerge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge }
var arrayMerge = options.arrayMerge || defaultArrayMerge
if (array) {
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
} else {
return mergeObject(target, source, optionsArgument)
}
}
deepmerge.all = function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array) || array.length < 2) {
throw new Error('first argument should be an array with at least two elements')
}
// we are sure there are at least 2 values, so it is safe to have no initial value
return array.reduce(function(prev, next) {
return deepmerge(prev, next, optionsArgument)
})
}
Little code with big functionality? That's my favorite type of utility!
The post JavaScript Deep Merge appeared first on David Walsh Blog.
Continue reading...
const defaultPerson = {
name: 'Anon',
gender: 'Female',
hair: {
color: 'brown',
cut: 'long'
},
eyes: 'blue',
family: ['mom', 'dad']
};
const me = {
name: 'David Walsh',
gender: 'Male',
hair: {
cut: 'short'
},
family: ['wife', 'kids', 'dog']
};
const summary = {...defaultPerson, ...me};
/*
{
"name":"David Walsh",
"gender":"Male",
"hair":{
"cut":"short"
},
"eyes":"blue",
"family":[
"wife",
"kids",
"dog"
]
}
*/
In the sample above, you’ll notice that the
hair
object’s color
is gone instead of merged because the spread operator simply keeps the last provided values, which in this case is me.hair
. The same merge problem applies to arrays — you’ll notice mom
and dad
aren’t merged from the defaultPerson
object’s family
array. Yikes!Deep merging in JavaScript is important, especially with the common practice of “default” or “options” objects with many properties and nested objects that often get merged with instance-specific values. If you’re looking for a utility to help with deep merges, look no further than the tiny deepmerge utility!
When you use the
deepmerge
utility, you can recursively merge any number of objects (including arrays) into one final object. Let’s take a look!const deepmerge = require('deepmerge');
// ...
const summary = deepmerge(defaultPerson, me);
/*
{
"name":"David Walsh",
"gender":"Male",
"hair":{
"color":"brown",
"cut":"short"
},
"eyes":"blue",
"family":[
"mom",
"dad",
"wife",
"kids",
"dog"
]
}
*/
deepmerge
can handle much more complicated merges: nested objects and deepmerge.all
to merge more than two objects:const result = deepmerge.all([,
{ level1: { level2: { name: 'David', parts: ['head', 'shoulders'] } } },
{ level1: { level2: { face: 'meh', parts: ['knees', 'toes'] } } },
{ level1: { level2: { eyes: 'more meh', parts: ['eyes'] } } },
]);
/*
{
"level1":{
"level2":{
"name":"David",
"parts":[
"head",
"shoulders",
"knees",
"toes",
"eyes"
],
"face":"meh",
"eyes":"more meh"
}
}
}
*/
deepmerge
is an amazing utility is a relatively small amount of code:function isMergeableObject(val) {
var nonNullObject = val && typeof val === 'object'
return nonNullObject
&& Object.prototype.toString.call(val) !== '[object RegExp]'
&& Object.prototype.toString.call(val) !== '[object Date]'
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true
return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
}
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice()
source.forEach(function(e, i) {
if (typeof destination === 'undefined') {
destination = cloneIfNecessary(e, optionsArgument)
} else if (isMergeableObject(e)) {
destination = deepmerge(target, e, optionsArgument)
} else if (target.indexOf(e) === -1) {
destination.push(cloneIfNecessary(e, optionsArgument))
}
})
return destination
}
function mergeObject(target, source, optionsArgument) {
var destination = {}
if (isMergeableObject(target)) {
Object.keys(target).forEach(function (key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument)
})
}
Object.keys(source).forEach(function (key) {
if (!isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument)
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument)
}
})
return destination
}
function deepmerge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge }
var arrayMerge = options.arrayMerge || defaultArrayMerge
if (array) {
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
} else {
return mergeObject(target, source, optionsArgument)
}
}
deepmerge.all = function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array) || array.length < 2) {
throw new Error('first argument should be an array with at least two elements')
}
// we are sure there are at least 2 values, so it is safe to have no initial value
return array.reduce(function(prev, next) {
return deepmerge(prev, next, optionsArgument)
})
}
Little code with big functionality? That's my favorite type of utility!
deepmerge
is used all over the web and for good reason!The post JavaScript Deep Merge appeared first on David Walsh Blog.
Continue reading...