All about object copying in JavaScript

All about object copying in JavaScript

How often did you attempt to clone an item in Javascript and the result wasn’t the very thing you anticipated? In this article, we will clarify the essential ideas driving cloning to guarantee that you generally utilize the best choice.

Differentiating between Shallow Copy and Deep Copying

Javascript has 2 types of object copy:

1. Shallow Copy: implies that the first level of the item is replicated. Deeper levels are referred.

2. Deep Copy: means that all levels of the object are copied. This is a true copy of the object.

For an in-depth experience, I asked ChatGPT to generate a nested object with all possible data types and it returned this:

const nestedObject1 = {
string: 'Hello, world!',
number: 42,
boolean: true,
nullValue: null,
undefinedValue: undefined,
array: ['apple', 'banana', 'cherry'],
object: {
nestedString: 'This is a nested string',
nestedNumber: 123,
nestedBoolean: false
},
date: new Date(),
regexp: /hello/g,
function: function () {
console.log('This is a function');
},
symbol: Symbol('symbol')
};

Object Assign:

The most basic and the worst method to copy is using the equals(=) operator.

let object2 = nestedObject1
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

This method doesn’t create a new object. Instead, it just creates a new reference to the old object itself. Any update to object2 massively impacts object1 and vice versa.

Shallow Copy:

To achieve shallow copy, we can use the spread operator ({…}) or the Object. assign() function.

  1. Spread Operator
let object2 = { ...nestedObject1 };
object2.string = "Hello from object2";
object2.number = 45;
object2.array[2] = "Strawberry";
object2.object.nestedNumber = 66;

If we look at the result we see an interesting thing:

The level 1 changes in object2 had no change in the fields of object2. However, the deeper level changes impacted both the objects and changed both values.

This is because the shallow copy just created another pointer to the deeper levels and hence creating a reference and not a copy.

2. Object.assign() function

let object2 = Object.assign({}, nestedObject1)
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

and the result

We see a similar result to the spread operator.

Deep Copy

A deep copy refers to creating a new copy of an object or array that is completely independent of the original, so any changes made to the copy will not affect the original, and vice versa. A deep copy ensures that all properties and nested objects or arrays are also copied, rather than just copying the reference to the original object or array.

To achieve a deep copy we shall be looking at the following methods:
- JSON.parse and JSON.stringify hack
- using a custom function
- using 3rd party library (lodash)
- inbuilt API

  1. JSON.parse and JSON.stringify hack

To use this, we use JSON.parse in succession to JSON.stringify like this:

let object2 = JSON.parse(JSON.stringify(nestedObject1))
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

To which, we get this output

As we can see the new object suffered massive data loss. Along with this, the methods like date suffered a type change.
Hence this method is highly inefficient when dealing with non primitive data types such as functions, regular expressions, symbols, undefined etc.

  1. Using a custom function

I created a deep copy function named deepcopy() and wrote a bunch of code, and implemented the same changes.

function deepCopy(obj) 
{if (typeof obj !== 'object' || obj === null) 
{return obj;}
let copy = Array.isArray(obj) ? [] : {};
Object.keys(obj)
.forEach((key) => {copy[key] = deepCopy(obj[key]);});return copy;}
let object2 = deepCopy(nestedObject1)
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

We get the result

As we can see, this method also caused a massive data loss similar to the JSON.parse & JSON.stringify method.

3. Using 3rd party library (lodash)

So far, this library returned a very good data copy with the most minimal data loss and maximum efficiency.
You can check it out at https://lodash.com/

To use this library, you need npm or yarn and a module-type script.

const load = require('lodash');
const object2 = load.cloneDeep(nestedObject1);
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

The result is as follows

This was the most efficient method to make a copy. However, the challenge here remains that, not always do we need a package or module format javascript. For this, there exists the next method.

4. Using an inbuilt API

Using the structuredClone API, we can achieve a deep copy of objects with minimal data loss. However, this method cannot perform a copy on functions, or symbols. So to modify the object, we now have

const nestedObject1 = {
string: 'Hello, world!',
number: 42,
boolean: true,
nullValue: null,
undefinedValue: undefined,
array: ['apple', 'banana', 'cherry'],
object: {
    nestedString: 'This is a nested string',nestedNumber: 123,
    nestedBoolean: false
    },
date: new Date(),
regexp: /hello/g
}

let object2 = structuredClone(nestedObject1)
object2.string = "Hello from object2"
object2.number = 45
object2.array[2] = "Strawberry"
object2.object.nestedNumber = 66

To this, we get the following result

The structuredClone API works on browsers, and given the above-mentioned constraints, this still has an optimal solution provided all sorts of limitations.

Hope you all found the blog informative and now have learned in depth about different methods of object copying.