Merging Multiple Objects in JavaScript

Abstract image used to represent Merging Multiple Objects in JavaScript
Image by Eric Prouzet.

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:

    Like Object.assign(), it doesn't mutate the original objects.
  • Shallow Merge:

    Similar to Object.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 hasOwnProperty checks 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...in loops 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, lastwritewins 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.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.