Merging Multiple Objects in JavaScript

Merging JavaScript objects is straightforward only when "last value wins" and a shallow copy are the intended rules. Configuration, application state, and API data often contain nested values or meaningful collisions, so the merge behaviour needs to be decided before choosing the spread operator, Object.assign(), or a custom function.
What Does It Mean to Merge Objects?
Merging objects involves combining the properties of two or more objects into a single object. If properties share the same key, the value from the latter object typically overwrites the former, for example:
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
// Merged result
const merged = { a: 1, b: 3, c: 4 };Using Object.assign() (ES6)
Introduced in ES6 (2015), Object.assign() is a widely supported method for merging objects together:
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // Output: { a: 1, b: 3, c: 4 }Key Points About Object.assign()
- It mutates the target object (the first argument).
- To prevent mutation, you need to pass an empty object
{}as the first argument. - It only performs a shallow copy, which means that nested objects are not deeply cloned:
const obj1 = { a: { value: 1 } };
const obj2 = { a: { value: 2 } };
const merged = Object.assign({}, obj1, obj2);
console.log(merged); // Output: { a: { value: 2 } }Using the Spread Operator (ES2018)
The object spread syntax, introduced in ES2018, offers a cleaner and more concise way to merge objects, although browser support can still be a little patchy in older browsers. Using spread syntax looks like this:
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // Output: { a: 1, b: 3, c: 4 }Advantages of the Spread Operator
Readability:
The syntax is fairly concise and intuitive.Immutability:
LikeObject.assign(), it doesn't mutate the original objects.Shallow Merge:
Similar toObject.assign(), it doesn't deeply clone nested objects.
Nested Objects and Shallow Merging
const obj1 = { a: { value: 1 } };
const obj2 = { a: { value: 2 } };
const merged = { ...obj1, ...obj2 };
console.log(merged); // Output: { a: { value: 2 } }In this example, the a property from obj2 completely overwrites the a property from obj1.
Merging Objects Without ES6 Features
For environments where we don't yet have support for ES6 features (like older browsers), you can merge objects using loops.
Using a for...in Loop
const mergeObjects = (...objects: any[]): any => {
const result: any = {};
objects.forEach(obj => {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = obj[key];
}
}
});
return result;
};
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
console.log(mergeObjects(obj1, obj2)); // Output: { a: 1, b: 3, c: 4 }This will do much the same as the more modern offerings from .assign() and the spread operator, but will also be supported by all those pesky browsers you still need to support, even though they are thoroughly out of date.
The fact that I've used ES6 arrow syntax to share this example with you has not passed me by either. You'll probably not want to use it in quite this form if you are targeting browsers that don't support ES6!
Considerations for Older Methods
- More verbose compared to modern alternatives and perhaps harder to maintain.
- Requires
hasOwnPropertychecks to avoid copying inherited properties. - Still only performs a shallow merge.
Wrapping Up
Key Takeaways
- Use
Object.assign()for reliable, shallow merging in ES6 environments. - The spread operator (
...) introduced in ES2018 offers cleaner syntax and avoids mutation. - For older browsers,
for...inloops provide a compatible, albeit verbose, solution. - Deep merging isn't natively supported (as of 2018); consider libraries like Lodash for complex data structures.
Object spread and Object.assign() both perform shallow, last‑write‑wins merges. That is fine when the collision rule is intentional and nested objects should remain shared. If nested data needs combining, define that behaviour explicitly rather than assuming a shallow merge has protected it.