TypeScript

3 Simple Ways to Merge Objects in TypeScript

Picture of me, Omari
Omari Thompson-Edwards
Fri Jan 26 2024 5 min

A common issue you might run into in your TypeScript projects is how to merge and modify objects. There are a few different options for this, so in this article I'd like to discuss 3 great, simple options, and the pros and cons of each.

Option 1 - Spread.

The easiest solution to merge objects in TypeScript or JavaScript is the spread syntax, or spread operator. If you're not familiar with spread, it essentially turns an object, or anything that is iterable into separate elements. 

You might have seen this trick before for cloning an object:

const myObject = {
    foo: 'bar',
    baz: 'qux',
};

const newObject = { ...myObject };
console.log(newObject === myObject); //false - different references

It turns "myObject" into a list of properties, and then uses the object literal syntax to create a new object from those properties.

There's nothing to stop us from spreading two or more objects in one object literal though, which lets us merge objects:

const objectA = { foo: 'foo' };
const objectB = { bar: 'bar' };

const merged = { ...objectA, ...objectB };

console.log(merged); // { foo: "foo", bar: "bar" }

One important thing to note is the type we get out here:

type ObjectA = { foo: string };
type ObjectB = { bar: number };
const objectA: ObjectA = { foo: 'foo' };
const objectB: ObjectB = { bar: 7 };

const merged = { ...objectA, ...objectB };
/*
    const merged: {
    bar: number;
    foo: string;
}
*/

TypeScript automatically creates a type that combines all of the properties of the merged types.

The syntax is pretty simple - The main downside to this approach is that it creates a shallow copy. The main downside to this. Let's take a look with an example. Imagine you have some kind of colour palette for your app. We'll define some default colours:

const defaultColourPalette = {
    light: {
        primary: 'purple',
        background: 'white',
    },
    dark: { primary: 'purple', background: 'black' },
};

And then create variants for different modes:

const devTheme = {
    mode: 'dark',
    ...defaultColourPalette,
};

const userTheme = {
    mode: 'light',
    ...defaultColourPalette,
};

Pretty straightforward so far. Let's try and modify one of our themes:

devTheme.light.primary = 'green';

And then let's print our two themes:

console.log('Dev:', devTheme);
console.log('User:', userTheme);

Code screenshot showing two objects. The two objects show a mock colour palette, where one colour value has been modified.

Weird - we modified a colour on one, but the change is showing up on both. Why's this? This is what a shallow copy means - all of the properties get copied over, but the references are the same. "devTheme.light" and "userTheme.light" both point to the same object.

So how do we get around this?

Option 2 - Lodash Merge.

One option is lodash's "merge" function. If you're not familiar with lodash, it's a large library of utility functions. You can install lodash with:

npm install lodash
yarn add lodash
pnpm add lodash

Here's the above example in Lodash:

import { merge } from 'lodash';

const devTheme = merge({ mode: 'dark' }, defaultColourPalette);
const userTheme = merge({ mode: 'light' }, defaultColourPalette);

And since lodash does a deep copy, that fixes our issue from before:

Code screenshot showing two objects. The two objects show a mock colour palette, where one colour value has been modified, but only on the first object.

Option 3- Object.assign.

There's one more option for merging objects in TypeScript you might be interested in -  the built in function "Object.assign". Similarly to the spread syntax, it creates shallow copies of the values in the object.

Here's the same example using "Object.assign":

const devTheme = Object.assign({ mode: 'dark' }, defaultColourPalette);
const userTheme = Object.assign({ mode: 'light' }, defaultColourPalette);

It accepts a target object which it will merge any number of objects into.

Or if you want to create a new object:


const devTheme = Object.assign({}, { mode: 'dark' }, defaultColourPalette);
const userTheme = Object.assign({}, { mode: 'light' }, defaultColourPalette);

You can change the target object to an empty object.

Types.

We mentioned before that with the spread syntax, you'll get back a type that merges all of the properties together. With both Lodash's merge function, and Object.assign, the type looks slightly different. You'll get back a type union of the merged types. Here's a comparison of the three:

const devThemeSpread = { mode: 'dark', ...defaultColourPalette };
const devThemeAssign = Object.assign({ mode: 'dark' }, defaultColourPalette);
const devThemeMerge = merge({ mode: 'dark' }, defaultColourPalette);

const devThemeSpread: {
    light: {
        primary: string;
        background: string;
    };
    dark: {
        primary: string;
        background: string;
    };
    mode: string;
}

const devThemeAssign: {
    mode: string;
} & {
    light: {
        primary: string;
        background: string;
    };
    dark: {
        primary: string;
        background: string;
    };
}

const devThemeMerge: {
    mode: string;
} & {
    light: {
        primary: string;
        background: string;
    };
    dark: {
        primary: string;
        background: string;
    };
}

Instead of a merging all of the properties, you get a type union - they're essentially identical, but the second option retains the information about the separate types that were merged.

Conclusion.

So to sum things up, to merge objects in TypeScript, you can use either the spread syntax, Lodash's merge function, or Object.assign.

So which one should you use? 

  • The spread operator is simple to use, but performs shallow copies
  • Object.assign is a bit more verbose, but lets you merge one object into another without creating a new one
  • Lodash merge does a deep merge/clone, but is an additional dependency

Personally, I tend to use the spread operator in most cases, and if I ever need a deep copy, Lodash is my go-to. 

Thanks for reading! I hope this article was helpful enough that you know everything you need to know for merging objects in TypeScript, and you're confident to know which approach works well for what scenarios. If you liked this article, feel free to share it!

read more.

Me
Hey, I'm Omari 👋

I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, feel free to reach out.