JavaScript Deep Merge

C

codeguru

Member
Freecoin
295
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 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...
 

Richest Freecoded User

Most Freecoin

freecoded
freecoded
2,182 Freecoin
Davy200
Davy200
590 Freecoin
nathan69
nathan69
424 Freecoin
Laureine
Laureine
415 Freecoin
C
codeguru
295 Freecoin
Tekera
Tekera
263 Freecoin
A
Akubay
170 Freecoin
smitha
smitha
104 Freecoin
G
Gabby
93 Freecoin
S
sesli
78 Freecoin
Top