Deep clone in TypeScript (persistent types)

I need to deeply clone an object in TypeScript. This should not be a problem, since libraries such as Lodash provide appropriate functions for this. However, they seem to be giving up type information.

> var a = new SomeClass(); > a instanceof SomeClass; < true > var b = _.cloneDeep(a); > b instanceof SomeClass; < false 

Is there a way to clone objects in TypeScript while storing this information for input?

+8
javascript lodash typescript
source share
4 answers

Typescript does not discard type information. In the DefinitelyTyped lodash.d.ts file, you can see that cloneDeep is defined as

 cloneDeep<T>( val: T, customizer?: (value: any) => any, thisArg?: any ) : T 

Ignoring arguments that we don’t need, it takes input T and spills out the output of T Thus, Typescript does not lose type information; he thinks the output of cloneDeep will be the same type as the input.

You should be able to verify this through your editor: if you have an editor that allows you to check the type of variables or autocomplete methods (which I highly recommend if you do not).


Why then does typeof not work as you expect? This is because Typescript type information is not carried at runtime. instanceof is a native JS statement that Typescript does not change the behavior you can see by running this snippet:

 "use strict"; class A {} let a = new A(); let b = _.cloneDeep(a); if (b instanceof A) { alert("b is an instance of A"); } else { alert("b is not an instance of A"); } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script> 

The reason b instanceof A is false is because instanceof checks the constructors: x instanceof A returns true if function A is a constructor somewhere in the x prototype chain (see the MDN documentation on instanceof ). However, Lodash does not use constructors when cloning objects. It's impossible. (How does he know which arguments should pass?) He creates a simple JS object that has all the methods of the cloned object, but does not reproduce the prototype chain.

Lodash clone (and most lodash methods, really) are best used when working with raw JS objects. If you use it in conjunction with constructors and instanceof , the check becomes a little muddy.


One solution is to avoid instanceof checking and do something like duck print; do not check that the constructor of an object is a specific function, but make sure that the object has the properties that you expect from it.

Another solution, as suggested in the comments, is to implement the clone method for your class that will not use lodash.

 class A() { clone() { var cloned = new A(); //pass appropriate constructor args //make other necessary changes to make the state match return cloned; } } 
+7
source share

There is an interesting blog post about deep cloning http://blog.soulserv.net/understanding-object-cloning-in-javascript-part-ii/ . You can use the author's implementation of the deep clone function clone() :

 class SomeClass { constructor(public test) { } } let c = new SomeClass("Hey!"); c.test = "Hey!"; console.log(c instanceof SomeClass); // returns true let cloneC = clone(c, /* resolve circular references */ true); console.log(cloneC instanceof SomeClass); // returns true 

[ clone () source code ] [ full source code ]

+5
source share

You can use the Lodash # cloneDeep utility . Usage example:

 import * as _ from "lodash"; ... { this.cloned = _.cloneDeep(data); } 
+1
source share

You can create your own deep clone using the JSON stringify and parse methods:

 export function deepClone<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)) as T; } 
0
source share

All Articles