
Copying objects in JavaScript - wheresvic1
https://smalldata.tech/blog/2018/11/01/copying-objects-in-javascript
======
mrgalaxy
I've been programming JS for a very long time and have learned to just stop
trying to do a generic deep copy. Since JS is a dynamically typed language, it
will always lead to issues down the road. Instead I write domain specific
merge methods for whatever objects I'm merging.

    
    
        function mergeOptions(...options) {
          const result = {};
    
          for (const opt of options) {
            result = {
              ...result,
              ...opt
              arrayValue: [
                ...(result.arrayValue || []),
                ...(opt.arrayValue || [])
              ],
              deepObject: {
                ...result.deepObject,
                ...opt.deepObject
              }
            };
          }
    
          return result;
        }
    

Know the shape of your objects and merging deeply becomes painless and won't
have edge-cases.

~~~
ben509
Another way of looking at it: if you are frequently doing complex copies, you
probably want immutable types.

~~~
throwaway645738
Im away from my usual PCs, and Im here just to say when I realized this exact
same thing it all clicked for me on why to use immutable objects

------
Null-Set
As of version 8.0.0 node has exposed a serialization api which is compatible
with structured clone.
[https://nodejs.org/api/v8.html#v8_serialization_api](https://nodejs.org/api/v8.html#v8_serialization_api)

    
    
        const v8 = require('v8');
        const buf = v8.serialize({a: 'foo', b: new Date()});
        const cloned = v8.deserialize(buf);
        cloned.b.getMonth();

~~~
devoply
Have we learned nothing from Java's serialization fiasco?

~~~
nur0n
I want to learn, can you elaborate?

~~~
fulafel
Java's deserialization will instantiate any classes that the data tells it to,
which in practice leads to myriad vulnerabilities as many classes have
constructors that can be used to write files, execute shell commands, etc.
Many programmers didn't realize this, and bad things happened.

This is a classic example:
[https://www.cvedetails.com/cve/CVE-2015-7501/](https://www.cvedetails.com/cve/CVE-2015-7501/)

(Many more can be found under the CWE-502 "Deserialization of Untrusted Data"
category)

------
malcolmwhite
Using structured cloning for deep copies is clever, but may or may not give
you the behavior you want for SharedArrayBuffers. The copied value would be a
new SAB with the same underlying data buffer, so that changes to one value
will be visible to the other. That's good for most uses of structured cloning,
but it's not what I would expect from a deep copy.

[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#Allocating_and_sharing_memory)

~~~
bcoates
Programs that use SharedArrayBuffers are already defective, so it's no big
deal.

~~~
yzmtf2008
Not true. Chrome has enabled SharedArrayBuffers again as of Chrome 68.

~~~
bzbarsky
Desktop-only, not on Android, right?

Also, a program that only works in one browser, and only due to that browser
being willing to open security holes other browsers are not willing to open,
is arguably still defective. That said, that might describe a lot of things
people are doing nowadays (e.g. anything that uses WebUSB).

------
bcoates
Fundamentaly, deep-copy in Javascript is a typed operation and no "generic"
deep-copy algorithm is possible--you have to know what meaning the value is
supposed to have to copy it.

There's nothing inherently wrong with structured clone, but it's only for
JSON-able objects with extensions for circular references and some built in
value-like classes. It's also special-cased for safe transmission between
Javascript domains so it has a bunch of undesirable behavior for local copies.
(no dom, no functions, no symbols, no properties...)

Even primitive types can't be safely copied unless you know what they're going
to be used for later, a nodejs file descriptor is just an integer but it's
also a reference to an OS resource that can't be duplicated without a syscall.

~~~
amelius
> Fundamentaly, deep-copy in Javascript is a typed operation and no "generic"
> deep-copy algorithm is possible--you have to know what meaning the value is
> supposed to have to copy it.

Why? I see no problem if the deep-copy behaves exactly the same as the
original, from the perspective of any operation in the Javascript API (except
for the === operator).

~~~
bcoates
Exactly the same as the original is a shallow copy.

Deep copy roughly means "If I do `dst = deepcopy(src)`, modifying anything in
the world I can reach through a reference from dst should have no side effect
visible through src" which is a reasonable thing to ask in some special cases
(a tree of plain old javascript values, a DOM node) but not reasonable in
others (a database connection object, a file descriptor, a user-id)

Like, what should a deep copy function do if it reaches a reference to the
global object? or a function that closes over some references in the object
being copied?

The answer is always "it depends on what the object will be used for later and
what specific behavior you want"

------
dan-robertson
It is fundamentally very hard (impossible?) to deep copy everything in
JavaScript. References cannot be escaped because they may be hidden in non-
introspectable (cruicially, non cloneabel) places. Viz:

    
    
      function f(){var x = 0; return function(){return x++;};}
      var x = {foo:f()};
      print(x.foo());
      var y = {foo:f()};
      print(y.foo());
      var z = someDeepCopy(y);
      print(z.foo());
      print(x.foo());
      print(y.foo());
      print(z.foo());
    

If a copy were sufficiently deep then one could expect:

    
    
      0
      0
      1
      1
      1
      2
    

However if it were not deep one would get:

    
    
      0
      0
      1
      1
      2
      3
    

Even if one allows a deep copying of closures then this still might not work
as an object which contains two (potentially different) functions closing over
the same binding (ie particular instance of a particular variable) may be
copied into two functions each closing over their own separate binding.

I think the only good solution to this is to either give up trying to do deep
copies or give up immutability and stop caring about deep copies.

------
russellbeattie
The language really should have a true immutable type (without freezing, etc.)
and deep copy method built in, with as many caveats and parameters as needed.
Coroutines would be awesome as well. (Yes, I'm thinking, "How could JavaScript
be more like Go or Erlang?")

And then it needs to stop adding new features for at least a couple years so
the world can catch up.

~~~
yuchi
I infer you don't mean to have both immutable data structures and deep copy as
features to use together, since immutables don't need to be cloned.

------
nobody271
var copy = JSON.parse(JSON.stringify(myObj));

Anything beyond this and you are begging for trouble because there's always
context-specific gotchas.

~~~
pnevares
This is presented in the linked document with the following context-specific
gotchas:

> Unfortunately, this method only works when the source object contains
> serializable value types and does not have any circular references. An
> example of a non-serializable value type is the Date object - it is printed
> in a non ISO-standard format and cannot be parsed back to its original value
> :(.

~~~
simlevesque
That's false. If you use JSON.stringify on an object, it will call the method
toJSON of each values recursively. The toJSON method of a Date returns the ISO
string.

source: [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description)
[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON)

~~~
negativegate
But JSON.parse will leave it as a string instead of converting it back to a
date, so this cloning approach doesn't work for objects containing dates.

~~~
simlevesque
I did not say that JSON.parse would parse the dates.

I'm saying that the article is wrong when it says that JSON.stringify does not
transform Dates into ISO string.

Right after that, the article says "cannot be parsed back to its original
value" when it definitely can be parsed back. JSON.parse does not do it by
default but that was never his point.

~~~
twblalock
> Right after that, the article says "cannot be parsed back to its original
> value" when it definitely can be parsed back. JSON.parse does not do it by
> default but that was never his point.

The broader point, which is entirely correct, is that you don't get back an
exact copy of the object you want to clone, because the date fields don't end
up being the same type.

------
austincheney
Why? Why would people want to clone objects? When I have encountered this in
the past it is from people who are new to the language.

My advise to any person who really believes they need a clone of an object: do
some self-reflection on plan as to why you think you need a cloned object. Any
other approach is more efficient and more simple in the code.

Objects are hash maps that store data.

------
sebringj
Yah that's why I use the serializable override:

someObj.toJSON = function() { return { foo: this.foo, bar: this.bar } }

It is an extra step but when using redux or something like that you have to
serialize stuff anyway to store it and is especially useful for mobile react-
native stuff in keeping state when phone restarts or connection fails.

------
Scarbutt
Another option if you can afford it is to just use immutablejs.

Or make your functions return new objects.

~~~
realharo
I would recommend immer
([https://github.com/mweststrate/immer](https://github.com/mweststrate/immer))
instead of ImmutableJS. You can work with regular JS objects, plus it plays
much nicer with TypeScript.

~~~
benvan
I wrote a tiny library called fn-update for this that does composable
functional updates ([https://github.com/benvan/fn-
update](https://github.com/benvan/fn-update)).

Personally, I prefer not having to mutate data, even wrapped in a produce
method

~~~
realharo
Having to specify the path using an array of strings would be an instant
dealbreaker for me. It messes with all tooling and is bad for readability.

------
ben509
Granted, this is Python, but I wrote this a while back:
[https://github.com/scooby/pyrsistent-
mutable](https://github.com/scooby/pyrsistent-mutable)

Basically, it's an AST translator that lets you use imperative syntax against
immutable types. That is, `x.a = b` becomes the clunky `x = x.set('a', b)`,
and it really gets convenient when you have complex structures.

Would it be worth it to look into a babel plugin for Javascript and
ImmutableJS?

------
chrisseaton
> objects in Javascript are simply references to a location in memory

No variables are simply references to objects. Objects aren't references -
they're referents.

------
barrystaes
I like the shallow copy, and never needed a deep copy. I am using JS for a few
years tops, mostly React.

To me its exactly what native languages do with pointers. In some languages
(like Delphi) its implicit (like JS) and some (like C) its explicit in syntax.

------
KaoruAoiShiho
Been using this for a while:
[https://stackoverflow.com/a/44612374/663447](https://stackoverflow.com/a/44612374/663447)
Best clone imo (for the correct usecases).

~~~
simlevesque
Don't use it with dates, it breaks them. You'll lose the data.

# var a = new Date();

# cloneDeep({ a }).a === { a }.a;

This returns false. Use JSON.stringify if you care about the content.
cloneDeep might be useful if you don't care about data integrity.

~~~
freeopinion
JSON.stringify also breaks dates.

~~~
simlevesque
No it does not. It returns a ISO string. You don't lose any info. And you can
parse it back with JSON.parse if you give it a reviver function. [1]

Do you want JSON.stringify to keep dates intact ? The whole point is to turn
it to a string.

[1] [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Parameters)

~~~
kbenson
"It works if you provide your own additional code that loops over and fixes
known problems" is not the same as "it works". It's useful, but at most it
saves you a few lines of boilerplate code (and perhaps a few negligible
cycles) to write your own recurser to do the same thing immediately after the
transform.

> Do you want JSON.stringify to keep dates intact ? The whole point is to turn
> it to a string.

There are ways to serialize data structures to strings. JSON doesn't generally
do that, and it's been to its benefit as it helps keep it generic and easily
usable from many languages.

That said, there are serialization tools which can correctly serialize and
unserialize more complex structures, given the correct circumstances (only
core constructs, or the objects in question have serialization helpers, or
they are ensured to not have features that might cause problems such as
references to outside data).

The point of this discussion is Javascript object cloning, not turning objects
into a string. Offering up JSON and then responding to a criticism of it based
on its methods as "the whole point is to turn it into a string" is somewhat
ridiculous given many of the other options you're espousing JSON over don't
use strings at all.

------
kalmi10
Fun fact: Not even the whole of number type is safe to clone with the JSON
method, because Infinity or NaN turn into null.

So one can’t infer JSON-clonability from TypeScript/JavaScript types. Learned
this the hard way.

------
z3t4
Premature optimizations is the root of all evil. That said, creating new
objects do slow your code down, so do it only when you have a good reason to.

------
iLemming
Every single time I see a similar article on Javascript, I feel so lucky for
being able to use Clojurescript instead. Seriously - Javascript platform is
great. The language itself? Not so nice. Clojurescript makes so many things
simply better.

------
stevebmark
Never do any of this. It's not 1990 anymore, we don't copy code from random
blog posts to solve problems.

------
freeopinion
> x = 4

4

> y = new (x.constructor)(x)

[Number: 4]

> x.constructor

[Function: Number]

> y.constructor

[Function: Number]

> typeof x

'number'

> typeof y

'object'

> x

4

> y

[Number: 4]

Is y a clone of x?

~~~
snek
no its boxed. take `y.valueOf()` and you've got a successful clone.

------
jackconnor
One of the weird, interesting parts of JS. Great article.

------
baybal2
20 years on, and still no native object copy and merge in JS

~~~
oweiler
Isn't Object.assign basically a merge?

~~~
RussianCow
I think the parent meant a _deep_ copy/merge—Object.assign only does a shallow
merge, which is the point of the article.

~~~
s_ngularity
What language does have a deep copy/merge operation built into the language?

~~~
Scarbutt
Most(all?) functional programming languages.

~~~
RussianCow
Most functional programming languages don't make a distinction between values
and references, so this is kind of a moot point since the copying/merging is
never exposed to the developer.

------
simlevesque
That article is wrong. A date can definitely be serialized in JS. It is in
fact converted to a ISO string when it is transformed into JSON, but the
article says it does not. The author needs to learn about JSON.stringify and
the toJSON method of built-ins.

~~~
fendrak
Indeed it can, but I believe the point is that it's not round-tripable,
meaning you can't parse the resulting serialized JSON back into its original
form, the one that contained the Date objects.

If you could do so, JSON.stringify/parse would be a convenient way to do a
deep clone.

~~~
zachrose
It would be cool if there was a webpage for “Is your function ______?” with a
list of things that functions can be:

\- Roundtrip-able (reversible?)

\- Pure

\- Effectful

\- Mutation-doing

\- Idempotent/Nullipotent

\- Algebraically closed over the set of its inputs

\- Et c.

Every time I hear about a new one I wish I had such a list

~~~
phiresky
> \- Roundtrip-able (reversible?)

You mean injective

