Skip to content

How to deep clone a JavaScript object

New Course Coming Soon:

Get Really Good at Git

JavaScript offers many ways to copy an object, but not all provide deep copy. Learn the most efficient way, and also find out all the options you have

Update 2022: just use structuredClone()

Copying objects in JavaScript can be tricky. Some ways perform a shallow copy, which is the default behavior in most of the cases.

Deep copy vs Shallow copy

A shallow copy successfully copies primitive types like numbers and strings, but any object reference will not be recursively copied, but instead the new, copied object will reference the same object.

If an object references other objects, when performing a shallow copy of the object, you copy the references to the external objects.

When performing a deep copy, those external objects are copied as well, so the new, cloned object is completely independent from the old one.

Searching how to deep clone an object in JavaScript on the internet, you’ll find lots of answers but the answers are not always correct.

Easiest option: use Lodash

My suggestion to perform deep copy is to rely on a library that’s well tested, very popular and carefully maintained: Lodash.

Lodash offers the very convenient clone and deepclone functions to perform shallow and deep cloning.

Lodash has this nice feature: you can import single functions separately in your project to reduce a lot the size of the dependency.

In Node.js:

const clone = require('lodash.clone')
const clonedeep = require('lodash.clonedeep')

Here is an example that shows those two functions in use:

const clone = require('lodash.clone')
const clonedeep = require('lodash.clonedeep')

const externalObject = {
  color: 'red',
}

const original = {
  a: new Date(),
  b: NaN,
  c: new Function(),
  d: undefined,
  e: function () {},
  f: Number,
  g: false,
  h: Infinity,
  i: externalObject,
}

const cloned = clone(original)

externalObject.color = 'blue'

console.info('⬇️ shallow cloning 🌈')
console.info(
  '✏️ Notice the i.color property we changed on original is also changed in the shallow copy'
)
console.log(original)
console.log(cloned)

const deepcloned = clonedeep(original)

externalObject.color = 'yellow'
console.log('')
console.info('⬇️ deep cloning 🌈')
console.info('✏️ Notice the i.color property does not propagate any more')
console.log(original)
console.log(deepcloned)

In this simple example we first create a shallow copy, and edit the i.color property, which propagates to the copied object.

In the deep clone, this does not happen.

Object.assign()

Object.assign() performs a shallow copy of an object, not a deep clone.

const copied = Object.assign({}, original)

Being a shallow copy, values are cloned, and objects references are copied (not the objects themselves), so if you edit an object property in the original object, that’s modified also in the copied object, since the referenced inner object is the same:

const original = {
  name: 'Fiesta',
  car: {
    color: 'blue',
  },
}
const copied = Object.assign({}, original)

original.name = 'Focus'
original.car.color = 'yellow'

copied.name //Fiesta
copied.car.color //yellow

Using the Object Spread operator

The spread operator is a ES6/ES2015 feature that provides a very convenient way to perform a shallow clone, equivalent to what Object.assign() does.

const copied = { ...original }

Wrong solutions

Online you will find many suggestions. Here are some wrong ones:

Using Object.create()

Note: not recommended

const copied = Object.create(original)

This is wrong, it’s not performing any copy.

Instead, the original object is being used as the prototype of copied.

Apparently it works, but under the hoods it’s not:

const original = {
  name: 'Fiesta',
}
const copied = Object.create(original)
copied.name //Fiesta

original.hasOwnProperty('name') //true
copied.hasOwnProperty('name') //false

See more on Object.create().

JSON serialization

Note: not recommended

Some recommend transforming to JSON:

const cloned = JSON.parse(JSON.stringify(original))

but that has unexpected consequences.

By doing this you will lose any Javascript property that has no equivalent type in JSON, like Function or Infinity. Any property that’s assigned to undefined will be ignored by JSON.stringify, causing them to be missed on the cloned object.

Also, some objects are converted to strings, like Date objects for example (also, not taking into account the timezone and defaulting to UTC), Set, Map and many others:

JSON.parse(
  JSON.stringify({
    a: new Date(),
    b: NaN,
    c: new Function(),
    d: undefined,
    e: function () {},
    f: Number,
    g: false,
    h: Infinity,
  })
)

Parsing as JSON

This only works if you do not have any inner objects and functions, but just values.

Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. A course that helps you feel less frustrated with Git. Launching Summer 2024. Join the waiting list!
→ Get my JavaScript Beginner's Handbook
→ Read my JavaScript Tutorials on The Valley of Code
→ Read my TypeScript Tutorial on The Valley of Code

Here is how can I help you: