Deep Clone an Object in TypeScript

When working with objects in TypeScript, sometimes a complete copy of an object is needed, including all its nested properties. When we copy an object along with its related properties, it is referred to as deep cloning.

Without deep cloning, if you change a nested property in the copied object, it might also change the original object. That can cause errors that are hard to track.

In this tutorial, I’ll explain how to deep clone objects in TypeScript while keeping their type information intact. We’ll cover simple ways like using JSON methods, a custom recursive deep clone function, and using popular libraries like Lodash.

Understanding Deep Cloning

Before we dive into the implementation, let’s understand what deep cloning means. Deep cloning involves creating a new object that is an exact replica of the original object, including all nested objects and their properties. This ensures that modifying the cloned object does not affect the original object or any of its nested objects.

Check out: TypeScript keyof Operator

The Problem with Shallow Copying In TypeScript

A common mistake developers make is using the assignment operator (=) or the spread operator (…) to create a copy of an object. However, these methods only perform a shallow copy, meaning that the top-level properties are copied by reference.

If the original object contains nested objects, the references to those nested objects are shared between the original and the copied object. This can lead to unintended modifications of the original object when modifying the copied object.

Consider the following example:

interface Person {
  name: string;
  age: number;
  address: {
    street: string;
    city: string;
  };
}

const john: Person = {
  name: 'John Doe',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'New York',
  },
};

const shallowCopy = { ...john };
shallowCopy.address.city = 'Los Angeles';

console.log(john.address.city); // Output: 'Los Angeles'

Output:

Deep Clone object in TypeScript

In this example, we create a shallow copy of the john object using the spread operator. However, when we modify the city property of the address object in the shallowCopy, it also affects the original john object. This is because the address object is shared by reference between john and shallowCopy.

Check out: Use For Loop Range in TypeScript

Deep Cloning Techniques in TypeScript

To achieve true deep cloning, we need to recursively copy all the nested objects and their properties. Here are a few techniques to deep clone an object in TypeScript:

1. JSON.parse and JSON.stringify

One simple approach to deep clone an object is to use the JSON.parse and JSON.stringify methods. By converting the object to a JSON string and then parsing it back into an object, we effectively create a deep clone of the object.

interface Address {
  city: string;
  street: string;
}

interface Person {
  name: string;
  age: number;
  address: Address;
}

const john: Person = {
  name: "John",
  age: 30,
  address: {
    city: "New York",
    street: "5th Avenue",
  },
};

function deepClone<T>(obj: T): T {
  return JSON.parse(JSON.stringify(obj));
}

const deepCopy = deepClone(john);
deepCopy.address.city = 'Chicago';

console.log(john.address.city);   // Output: 'New York'
console.log(deepCopy.address.city); // Output: 'Chicago'

Output:

Clone object in TypeScript

In this example, the deepClone function takes an object of type T and returns a deep clone of that object. By using JSON.stringify to convert the object to a JSON string and then JSON.parse to parse it back into an object, we achieve deep cloning.

However, this approach has some limitations:

  • It only works with serializable objects, meaning objects that can be converted to a JSON string.
  • It does not preserve the object’s prototype chain or any non-enumerable properties.
  • It may not handle circular references correctly.

Check out: Check for an Empty Object in TypeScript

2. Recursive Deep Cloning

A more robust approach to deep cloning is to implement a recursive function that traverses the object and its nested objects, creating new copies of each object along the way.

function deepClone<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  const clone = Array.isArray(obj) ? [] : {};
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      clone[key] = deepClone(obj[key]);
    }
  }

  return clone as T;
}

In this implementation, the deepClone function checks if the input obj is null or a primitive type. If so, it simply returns the input as is. If obj is an array or an object, it creates a new array or object accordingly.

Check out: Initialize Maps in TypeScript

The function then iterates over the properties of obj using a for…in loop. For each property, it recursively calls deepClone on the property value to create a deep clone of any nested objects. The cloned property is then assigned to the corresponding key in the clone object.

Finally, the function returns the clone object, which is a deep copy of the original object.

3. Third-Party Libraries

If you prefer not to implement the deep cloning logic yourself, there are several third-party libraries available that provide deep cloning functionality out of the box. One popular library is Lodash, which offers the cloneDeep function.

Install the library:

npm install lodash
npm install --save-dev @types/lodash

Execute code:

import { cloneDeep } from 'lodash';

interface Address {
  city: string;
  street: string;
}

interface Person {
  name: string;
  age: number;
  address: Address;
}

const john: Person = {
  name: "John",
  age: 30,
  address: {
    city: "New York",
    street: "5th Avenue",
  },
};

const deepCopy = cloneDeep(john);
deepCopy.address.city = 'San Francisco';

console.log(john.address.city);     // Output: 'New York'
console.log(deepCopy.address.city); // Output: 'San Francisco'

Output:

Deep Clone object in TypeScript using library

Using a library like Lodash can simplify the deep cloning process and provide additional features and optimizations.

Check out: Difference Between Record vs Map in TypeScript

Preserving Object Types

When deep cloning an object in TypeScript, it’s important to preserve the object’s type information. This ensures that the cloned object has the same type as the original object, allowing for type-safe operations and type checking.

The techniques we discussed earlier, such as using JSON.parse and JSON.stringify or implementing a recursive deep cloning function, preserve the object’s type by default. The cloned object will have the same type as the original object.

However, if you are using a third-party library for deep cloning, make sure to check its documentation to ensure that it preserves the object’s type information.

Check out: Exception Handling in TypeScript

Real-World Example

Let’s consider a real-world scenario where deep cloning can be useful. Suppose you are building a customer management system for a company based in the United States. Each customer has a name, age, and an address object containing their street and city information.

interface Address {
  street: string;
  city: string;
}

interface Customer {
  name: string;
  age: number;
  address: Address;
}

const customers: Customer[] = [
  {
    name: 'Jennifer Smith',
    age: 35,
    address: {
      street: '456 Elm St',
      city: 'Boston',
    },
  },
  {
    name: 'Michael Johnson',
    age: 42,
    address: {
      street: '789 Oak Ave',
      city: 'Chicago',
    },
  },
];

Now, let’s say you need to create a new array of customers with updated addresses, but you don’t want to modify the original customers array. Deep cloning comes to the rescue!

function deepClone<T>(obj: T): T { 
 return JSON.parse(JSON.stringify(obj));
}
const updatedCustomers = customers.map(deepClone);
updatedCustomers[0].address.city = 'New York';
updatedCustomers[1].address.city = 'Los Angeles';

console.log(customers[0].address.city); // Output: 'Boston'
console.log(updatedCustomers[0].address.city); // Output: 'New York'

Output:

deep cloning in TypeScript objects

Check out: TypeScript forEach Loop with Index

By using the deepClone function (either your own implementation or a third-party library), you can create a new array updatedCustomers that contains deep copies of the original customer objects. Modifying the addresses in updatedCustomers does not affect the original customers array, ensuring data integrity and avoiding unintended side effects.

Conclusion

In this tutorial, we have learned about the various techniques for deep cloning objects in TypeScript, including using JSON.parse and JSON.stringify, implementing a recursive deep cloning function, and leveraging third-party libraries like Lodash.

By following the methods in this tutorial, you can confidently work with deep cloning in TypeScript and build robust and maintainable applications.

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.