Deep Copy

May 20, 2023

A deep copy refers to the process of duplicating an object or data structure, including all its properties and sub-properties, recursively, so that any changes made to the duplicate do not affect the original object. This is in contrast to a shallow copy, which only creates a new object with references to the same properties as the original object.

The purpose of making a deep copy is to create a completely independent copy of an object or data structure, which can be modified without affecting the original object. This is particularly useful in programming languages where objects are passed by reference, such as JavaScript, as it allows for the creation of a new object with the same properties as the original, but without any references to the original object.

Deep copying can be used in a wide variety of contexts, including cloning objects, serialization, and data management. It is particularly useful when working with complex data structures that have a large number of sub-properties or when working with objects that are shared between multiple threads or processes.

Shallow Copy vs. Deep Copy

In order to understand the concept of deep copy, it is important to first understand the difference between shallow copy and deep copy.

Shallow copy refers to the process of copying an object or data structure, but only creating a new object that has references to the same properties as the original object. This means that any changes made to the original object will also affect the new object.

For example, consider the following code snippet in JavaScript:

const originalObject = { 
  name: "Alice", 
  age: 30, 
  address: { 
    street: "123 Main St", 
    city: "New York", 
    state: "NY" 
  } 
};

const shallowCopy = Object.assign({}, originalObject);

In this example, originalObject is an object with three properties: name, age, and address. The address property is itself an object with three sub-properties: street, city, and state.

The Object.assign() method is used to create a shallow copy of originalObject and assign it to the variable shallowCopy.

Now, let’s say we want to update the address of shallowCopy without affecting the address of originalObject. We can do this as follows:

shallowCopy.address.street = "456 Broadway";

If we were to log the values of originalObject and shallowCopy to the console, we would see that the address property of originalObject has also been updated:

console.log(originalObject.address.street); // Output: "456 Broadway"
console.log(shallowCopy.address.street); // Output: "456 Broadway"

This is because shallowCopy only contains a reference to the address property of originalObject, not a copy of it.

In contrast, a deep copy creates a new object that is completely independent of the original object, including all its properties and sub-properties. Any changes made to the new object will not affect the original object.

For example, consider the following code snippet:

const originalObject = { 
  name: "Alice", 
  age: 30, 
  address: { 
    street: "123 Main St", 
    city: "New York", 
    state: "NY" 
  } 
};

const deepCopy = JSON.parse(JSON.stringify(originalObject));

In this example, JSON.stringify() is used to convert originalObject to a string, and JSON.parse() is used to convert the string back to an object. This creates a deep copy of originalObject and assigns it to the variable deepCopy.

Now, let’s say we want to update the address of deepCopy without affecting the address of originalObject. We can do this as follows:

deepCopy.address.street = "456 Broadway";

If we were to log the values of originalObject and deepCopy to the console, we would see that the address property of originalObject has not been updated:

console.log(originalObject.address.street); // Output: "123 Main St"
console.log(deepCopy.address.street); // Output: "456 Broadway"

This is because deepCopy is completely independent of originalObject.

Usage

Deep copying can be used in a wide variety of contexts, including cloning objects, serialization, and data management.

Cloning Objects

One common use case for deep copying is in cloning objects. Cloning an object creates a new object with the same properties as the original object. This can be useful when you want to create a copy of an object to modify without affecting the original object.

For example, consider the following code snippet:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const alice = new Person("Alice", 30);
const bob = Object.assign({}, alice);

console.log(bob.name); // Output: "Alice"
console.log(bob.age); // Output: 30

In this example, we have a Person class with two properties: name and age. We create a new Person object named alice, and then use Object.assign() to create a shallow copy of alice and assign it to the variable bob.

If we were to modify the name property of bob, it would not affect the name property of alice. However, if we were to add a new property to bob, it would also be added to alice:

bob.name = "Bob";
bob.address = "123 Main St";

console.log(bob.name); // Output: "Bob"
console.log(bob.address); // Output: "123 Main St"
console.log(alice.name); // Output: "Alice"
console.log(alice.address); // Output: undefined

To create a deep copy of alice, we can use the JSON.parse() and JSON.stringify() methods, as shown in the previous example:

const alice = {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York",
    state: "NY"
  }
};

const bob = JSON.parse(JSON.stringify(alice));

console.log(bob.address.street); // Output: "123 Main St"

bob.address.street = "456 Broadway";

console.log(bob.address.street); // Output: "456 Broadway"
console.log(alice.address.street); // Output: "123 Main St"

In this example, alice is an object with three properties, including an address property that is itself an object with three sub-properties. We create a deep copy of alice named bob using JSON.parse() and JSON.stringify().

If we were to modify the address property of bob, it would not affect the address property of alice.

Serialization

Deep copying is also used in serialization, which is the process of converting an object or data structure into a format that can be stored or transmitted. Serialization is often used in web development to store data in cookies, local storage, or in the query string of a URL.

For example, consider the following code snippet:

const person = {
  name: "Alice",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York",
    state: "NY"
  }
};

const serializedPerson = JSON.stringify(person);

console.log(serializedPerson); // Output: "{\"name\":\"Alice\",\"age\":30,\"address\":{\"street\":\"123 Main St\",\"city\":\"New York\",\"state\":\"NY\"}}"

In this example, we have a person object with three properties, including an address property that is itself an object with three sub-properties. We use JSON.stringify() to convert person into a string that can be stored or transmitted.

If we were to modify the address property of person, it would not affect the serialized version of person.

To deserialize person, we can use JSON.parse(), as shown in the previous example:

const deserializedPerson = JSON.parse(serializedPerson);

console.log(deserializedPerson.address.street); // Output: "123 Main St"

In this example, we use JSON.parse() to convert the serialized version of person back into an object.

Data Management

Deep copying is also used in data management when working with complex data structures, such as arrays or objects with nested properties. When working with complex data structures, it is often necessary to create a deep copy of the data structure in order to modify it without affecting the original data structure.

For example, consider the following code snippet:

const data = {
  name: "Data",
  values: [1, 2, 3],
  nestedData: {
    name: "Nested Data",
    values: [4, 5, 6]
  }
};

const deepCopy = JSON.parse(JSON.stringify(data));

deepCopy.nestedData.values.push(7);

console.log(data.nestedData.values); // Output: [4, 5, 6]
console.log(deepCopy.nestedData.values); // Output: [4, 5, 6, 7]

In this example, we have a data object with three properties, including an values property that is an array, and a nestedData property that is itself an object with an values property that is also an array. We create a deep copy of data named deepCopy using JSON.parse() and JSON.stringify().

If we were to modify the values property of nestedData in deepCopy, it would not affect the values property of nestedData in data.

Limitations

While deep copying can be a useful technique, it is not without its limitations. One limitation of deep copying is that it can be resource-intensive, particularly when working with large data structures. This is because the entire data structure must be copied, including all its sub-properties.

Another limitation of deep copying is that it may not be able to copy certain types of objects or properties. For example, objects with circular references cannot be deep copied using JSON.stringify() and JSON.parse(), as they will result in an infinite loop.

Finally, deep copying may not always be necessary or desirable. In some cases, a shallow copy may be sufficient, or it may be better to modify the original object directly. Each situation will depend on the specific use case and the requirements of the application.