Both Object.assign and Object spread only do a shallow merge.
An example of the problem:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
The output is what you'd expect. However if I try this:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Instead of
{ a: { a: 1, b: 1 } }
you get
{ a: { b: 1 } }
x is completely overwritten because the spread syntax only goes one level deep. This is the same with Object.assign()
.
Is there a way to do this?
I know this is a bit of an old issue but the easiest solution in ES2015/ES6 I could come up with was actually quite simple, using Object.assign(),
Hopefully this helps:
Example usage:
You'll find an immutable version of this in the answer below.
Note that this will lead to infinite recursion on circular references. There's some great answers on here on how to detect circular references if you think you'd face this issue.
I know there's a lot of answers already and as many comments arguing they won't work. The only consensus is that it's so complicated that nobody made a standard for it. However, most of accepted answers in SO expose "simple tricks" that are widely used. So, for all of us like me who are no experts but want to write safer code by grasping a little more about javascript's complexity, I'll try to shed some light.
Before getting our hands dirty, let me clarify 2 points:
Object.assign
does.Answers with
for..in
orObject.keys
are misleadingMaking a deep copy seems so basic and common practice that we expect to find a one-liner or, at least, a quick win via simple recursion. We don't expect we should need a library or write a custom function of 100 lines.
When I first read Salakar's answer, I genuinely thought I could do better and simpler (you can compare it with
Object.assign
onx={a:1}, y={a:{b:1}}
). Then I read the8472's answer and I thought... there is no getting away so easily, improving already given answers won't get us far.Let's let deep copy and recursive aside an instant. Just consider how (wrongly) people parse properties to copy a very simple object.
Object.keys
will omit own non-enumerable properties, own symbol-keyed properties and all prototype's properties. It may be fine if your objects don't have any of those. But keep it mind thatObject.assign
handles own symbol-keyed enumerable properties. So your custom copy lost its bloom.for..in
will provide properties of the source, of its prototype and of the full prototype chain without you wanting it (or knowing it). Your target may end up with too many properties, mixing up prototype properties and own properties.If you're writing a general purpose function and you're not using
Object.getOwnPropertyDescriptors
,Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
orObject.getPrototypeOf
, you're most probably doing it wrong.Things to consider before writing your function
First, make sure you understand what a Javascript object is. In Javascript, an object is made of its own properties and a (parent) prototype object. The prototype object in turn is made of its own properties and a prototype object. And so on, defining a prototype chain.
A property is a pair of key (
string
orsymbol
) and descriptor (value
orget
/set
accessor, and attributes likeenumerable
).Finally, there are many types of objects. You may want to handle differently an object Object from an object Date or an object Function.
So, writing your deep copy, you should answer at least those questions:
For my example, I consider that only the
object Object
s are deep, because other objects created by other constructors may not be proper for an in-depth look. Customized from this SO.And I made an
options
object to choose what to copy (for demo purpose).Proposed function
You can test it in this plunker.
That can be used like this:
We can use $.extend(true,object1,object2) for deep merging. Value true denotes merge two objects recursively, modifying the first.
$extend(true,target,object)
It doesn't exist but you can use
JSON.parse(JSON.stringify(jobs))
If your are using ImmutableJS you can use
mergeDeep
:Here's another one I just wrote that supports arrays. It concats them.