This content originally appeared on Go Make Things and was authored by Go Make Things
Back in January, I wrote an article merging arrays and objects with JavaScript. At the end, I wrote…
Tomorrow, we’ll take a look at how to deep merge objects and arrays.
And then I never did! Today, we’re going to circle back and cover how to deep merge arrays and objects with vanilla JS.
Let’s dig in!
What is a deep merge?
In a deep merge, you merge not just the parent arrays or objects, but any nested arrays or objects as well.
Imagine you have two objects, like this…
let merlin = {
job: 'Wizard',
spells: ['Dancing teacups', 'Disappear'],
pet: 'owl'
};
let radagast = {
job: 'Druid',
spells: ['Talk to animals', 'Navigate'],
tool: 'staff',
age: 179
};
You merge them together using Object.assign()
method or spread operator…
let mergedWizards = {...merlin, ...gandalf};
In the resulting object, the spells
array contains just two items…
['Talks to animals', 'Navigate']
The spells
property in the radagast
object completely overwrites that same property in the merlin
object.
In a deep merge, the two arrays are combined instead of overwritten.
['Dancing teacups', 'Disappear', 'Talks to animals', 'Navigate']
Unfortunately, there’s currently no native JavaScript method for doing a deep merge, so we’ll need to create a helper function.
Creating a deepMerge() helper function
First, let’s create a deepMerge()
helper function.
Because we want to accept one or more arrays or objects as arguments, we’ll use a rest parameter, ...objs
, to catch them all and turn them into an array.
function deepMerge (...objs) {
// ...
}
Next, we’ll use the Array.shift()
method to get the first item from the objs
array and remove it from the array.
We’ll pass it into the structuredClone()
method to create a deep copy, and assign it to the clone
variable.
This is the object or array that we’ll merge all other items into, so we’ll return
it back out.
function deepMerge (...objs) {
// Create a clone of the first item in the objs array
let clone = structuredClone(objs.shift());
return clone;
}
Looping over the other arrays or objects
Next, we want to loop through each array or object in the objs
array, and merge their values into the clone
.
We’ll use a for...of
loop to do that.
function deepMerge (...objs) {
// Create a clone of the first item in the objs array
let clone = structuredClone(objs.shift());
// Loop through each item
for (let obj of objs) {
// ...
}
return clone;
}
Inside the loop, we want to determine if the obj
is an array, an object, or a primitive.
The typeof
operator returns object
for many types of items, not just plain objects ({}
). We’ll instead use the Object.prototype.toString.call()
approach.
To make this easier, we’ll add a getType()
helper function.
/**
* Get the object type
* @param {*} obj The object
* @return {String} The object type
*/
function getType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
Then, inside the loop, we’ll check the type
of the obj
and clone
.
If the clone
and obj
are not the same type of object, we’ll replace the clone
with the current obj
in the loop. Then we’ll continue
to skip to the next item.
This would only happen if you passed in, for example, an object as the first argument, and an array as the second.
function deepMerge (...objs) {
/**
* Get the object type
* @param {*} obj The object
* @return {String} The object type
*/
function getType (obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
// Create a clone of the first item in the objs array
let clone = structuredClone(objs.shift());
// Loop through each item
for (let obj of objs) {
// Get the object type
let type = getType(obj);
// If the current item isn't the same type as the clone, replace it
if (getType(clone) !== type) {
clone = structuredClone(obj);
continue;
}
}
return clone;
}
Merging arrays
If the type
of obj
is an array ([]
), we’ll pass the obj
into the structuredClone()
method to create a deep copy.
Then, we’ll use the spread operator to combine the clone
and obj
into a single array.
// Loop through each item
for (let obj of objs) {
// Get the object type
let type = getType(obj);
// If the current item isn't the same type as the clone, replace it
if (getType(clone) !== type) {
clone = structuredClone(obj);
continue;
}
// Otherwise, merge
if (type === 'array') {
clone = [...clone, ...structuredClone(obj)];
}
}
Merging objects
If the type
of obj
is an object ({}
), we need to loop through each item in it and do some additional checks.
To make that easier, let’s create a mergeObj()
function. We’ll pass the clone
and the current obj
into it as argument.
// Loop through each item
for (let obj of objs) {
// ...
// Otherwise, merge
if (type === 'array') {
clone = [...clone, ...structuredClone(obj)];
} else if (type === 'object') {
mergeObj(clone, obj);
}
}
Inside the mergeObj()
function, we’ll use the Object.entries()
method with array destructuring to loop over each item in the obj
and get the key
and value
.
/**
* Deep merge two objects
* @return {Object}
*/
function mergeObj (clone, obj) {
for (let [key, value] of Object.entries(obj)) {
// ...
}
}
For each item, we’ll create a copy of the value
using the structuredClone()
method, and assign that value to the matching key
in the clone
object.
/**
* Deep merge two objects
* @return {Object}
*/
function mergeObj (clone, obj) {
for (let [key, value] of Object.entries(obj)) {
clone[key] = structuredClone(value);
}
}
This is the same as doing a shallow merge, though.
If the key
already exists in the clone
, and both the current item at that key
and the new value
are of the same type, and the value
is an array or object, we want to recursively merge them together.
To do that, we’ll pass the clone[key]
and the value
back into the deepMerge()
function, and assign the returned value to the clone[key]
property.
/**
* Deep merge two objects
* @return {Object}
*/
function mergeObj (clone, obj) {
for (let [key, value] of Object.entries(obj)) {
let type = getType(value);
if (clone[key] !== undefined && getType(clone[key]) === type && ['array', 'object'].includes(type)) {
clone[key] = deepMerge(clone[key], value);
} else {
clone[key] = structuredClone(value);
}
}
}
Everything else
Finally, for all other object types, we’ll update the value of clone
to the current obj
.
// Loop through each item
for (let obj of objs) {
// ...
// Otherwise, merge
if (type === 'array') {
clone = [...clone, ...structuredClone(obj)];
} else if (type === 'object') {
mergeObj(clone, obj);
} else {
clone = obj;
}
}
Using the function
Now, we can pass both of our objects into the deepMerge()
function to get a deeply merged copy back…
let mergedWizards = deepMerge(merlin, gandalf);
You can download a copy of the completed helper function on the Vanilla JS Toolkit, and play with a demo on CodePen.
I have a favor to ask. If you enjoyed this article, could you share a link to my newsletter on your favorite social media site?
This content originally appeared on Go Make Things and was authored by Go Make Things
Go Make Things | Sciencx (2023-04-27T14:30:00+00:00) How to deep merge arrays and objects with JavaScript. Retrieved from https://www.scien.cx/2023/04/27/how-to-deep-merge-arrays-and-objects-with-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.