Objects Shallow Copy vs Deep Copy 💥

Objects Shallow Copy vs Deep Copy 💥

Introduction

Objects are a bit weird when it comes to cloning them. Objects when cloned may create a shallow copy or a deep copy depending on how it was cloned. In this blog, we will see the problem that a shallow copy creates and how we overcome it with deep copy, we will also learn different ways to create a deep copy of an object.

Shallow Copy 🐍

Let's say we have an object prakashDetails with keys state and country having some value, and now we want to create another object ashishDetails with a different state but the same country. So we can create a copy of the existing object prakashDetails and assign it to the new object ashishDetails and then change the state of the newly created object. Let's see this with an example👇

const prakashDetails = {
    state: "Maharashtra",
    country: "India"
}

// creating another object ashishDetails by using the above object
const ashishDetails = prakashDetails;

// Changing state of object ashishDetails
ashishDetails.state = "Haryana";
console.log(ashishDetails)
// Output: { state: "Haryana" , country: "India" }

Woaahh!!, we have created a new object by simply using an existing object, amazing right!!. But, there is a small problem, that is, if we check the properties of object prakashDetails it's value of state has also been changed,

console.log(prakashDetails);
// Output: { state: "Haryana" , country: "India"}

Now this is called a shallow copy, where changing property of one object changes the property of another object as well. We say that an object is a shallow copy of another object if both the object point/refer to the same address in the memory location

Why did the above happen? 🧐

This happens because when we try to copy an object the newly copied object also refers to the same memory location of the existing object. That means from the above example, both the objects prakashDetails and ashishDetails are having the same address in the memory. Hence changing a property using the object ashishDetails, will change the property in prakashDetails as well.

To solve this JavaScipt has a built-in method Object.assign() that will create a copy of the object, but at the same time if an object has a nested object then Object.assign() will create a shallow copy for that nested object. Let's see this in action👇

Object.assign() 😎

Note: Object.assign(target, source) -> all the property of source object will be copied to target object, target object can be an empty object or an already existing object

const prakashDetails = {
    state: "Maharashtra",
    country: "India"
}

const ashishDetails = Object.assign({}, prakashDetails);
console.log(ashishDetails)
// Output: { state: "Maharashtra", country: "India" }

// Changing state of object ashishDetails
ashishDetails.state = "Haryana";
console.log(ashishDetails)
// Output: { state: "Haryana" , country: "India" }

console.log(prakashDetails)
// Output: { state: "Maharashtra" , country: "India" }

Wooahh!! Object.assign() did solve our problem, did it, no, it did not completely solve our problem, what If there is a nested object? Then in such cases object.assign() will create a shallow copy of the nested object. Let's have a look at it👇

const prakashDetails = {
    state: "Maharashtra",
    country: "India",
    location: {
        city: "Mumbai",
        pincode: "123456"
    }
}
const ashishDetails = Object.assign({}, prakashDetails);
console.log(ashishDetails)
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"} }

// Changing state and city of object ashishDetails
ashishDetails.state = "Haryana";
ashishDetails.location.city = "Sirsa";
console.log(ashishDetails)
// Output: { state: "Haryana", country: "India", location: { city: "Sirsa",  pincode: "123456"} }

console.log(prakashDetails);
// Output: { state: "Maharashtra", country: "India", location: { city: "Sirsa",  pincode: "123456"} }

Damn it!! Even Object.assign() couldn't create a deep copy. If you see, the value of state are different but the value of city is the same, hence we can conclude that Object.assign() also creates a shallow copy.

How can we overcome this problem? 🧐🧐

To our rescue, we have something called a deep copy. With deep copy we copy the properties of an existing object to a new object, but the new object is created at a different address in the memory.

Deep copy can be created in multiple ways

  1. By using spread operator. 💥
  2. By using JSON.stringify() and JSON.parse(). 🔥
  3. By using Lodash 💪💪💪

Let's see all the methods with examples👇

Spread Operator 🍻

const prakashDetails = {
    state: "Maharashtra",
    country: "India",
    location: {
        city: "Mumbai",
        pincode: "123456"
    }
}
// creating another object ashishDetails by using the spread
const ashishDetails = {...prakashDetails, location: {...prakashDetails.location}}
console.log(ashishDetails)
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"} }

// Changing state and city of object ashishDetails
ashishDetails.state = "Haryana";
ashishDetails.location.city = "Sirsa";
console.log(ashishDetails)
// Output: { state: "Haryana", country: "India", location: { city: "Sirsa",  pincode: "123456"} }

console.log(prakashDetails);
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"} }
}

Ohh yess!! We got it, the spread operator worked fine and created a deep copy, but is it an optimised solution, certainly not, if there are multiple nested objects then we have to write the spread of each one of them and that is a time consuming affair.

Using JSON.stringify() and JSON.parse() 💥💥

const prakashDetails = {
    state: "Maharashtra",
    country: "India",
    location: {
        city: "Mumbai",
        pincode: "123456"
    }
}

// The target object is first converted to a string output
const stringifiedOutput = JSON.stringify(prakashDetails);

// Stringified Object is parsed back to some other Object
const ashishDetails = JSON.parse(stringifiedOutput);
console.log(ashishDetails)
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"} }

// Changing state and city of object ashishDetails
ashishDetails.state = "Haryana";
ashishDetails.location.city = "Sirsa";
console.log(ashishDetails)
// Output: { state: "Haryana", country: "India", location: { city: "Sirsa",  pincode: "123456"} }

console.log(prakashDetails);
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"} }

Yesss!!! This gives us the desired result we were waiting for, but, but, but this also has a CAVEAT.

Disadvantage of using JSON.stringify() and JSON.parse()

  1. If the property of an object is a function then JSON.stringify() and JSON.parse() will not copy the function into another object Let's see an example👇
const greetMessage  = {
     objectType: "greetings"
     getMessage: () => {
        console.log("Hello Lovely People❤️ ")
    }
}

const welcomeMessage = JSON.parse(JSON.stringify(greetMessage));
console.log(welcomeMessage);
// Output: { objectType: "greetings" }

Naah!!! There is again a problem, even JSON couldn't give us a proper deep clone, since it cannot copy the function, so we cannot create a deep copy with JSON as well.

Finally presenting our hero --> Lodash 💪💪💪

Lodash is a javascript module that has utility functions for strings, arrays, objects and much more. Do check Lodash documentation. You need to install lodash in order to use it, or else you can test the code on Replit

var lodash = require('lodash');
const prakashDetails = {
    state: "Maharashtra",
    country: "India",
    location: {
        city: "Mumbai",
        pincode: "123456"
    },
    greetMessage: () => console.log("Hello Lovely People❤️")
}
const ashishDetails = lodash.cloneDeep(prakashDetails);

ashishDetails.location.city = "Sirsa"
console.log(ashishDetails)
// Output: { state: "Haryana", country: "India", location: { city: "Sirsa",  pincode: "123456"}, greetMessage: () => console.log("Hello Lovely People❤️") }

console.log(prakashDetails);
// Output: { state: "Maharashtra", country: "India", location: { city: "Mumbai",  pincode: "123456"}, greetMessage: () => console.log("Hello Lovely People❤️") }

Finallyyy🍻, we got what we were searching for, the ultimate lodash solves our problem.

Summary 😎

  1. Shallow copy means multiple objects referring to the same address in a memory location.
  2. Object.assign() creates a shallow copy, it cannot be used to copy nested objects
  3. spread operator does the job but, then we have to spread each nested object which is a pain
  4. JSON.stringify() and JSON.parse() does create a deep copy but does not copy function from one object to another
  5. Lodash handles all the above gracefully, it creates a deep copy by copying all the properties from the source object into the target object.

Reference 🙌

  1. Objects MDN - developer.mozilla.org/en-US/docs/Web/JavaSc..
  2. Objects JavaScript Info - javascript.info/object-basics
  3. Lodash Docs - lodash.com/docs/4.17.15

Thank you for reading the blog, if you understood and learned something new let me know in the comments, I will be happy to read what you have understood. Moreover, you can connect with me on Twitter and LinkedIn. Cheers 🍻