Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep clone in TypeScript (preserving types)

I need to deep clone an object in TypeScript. This shouldn't be a problem as libraries like Lodash provide appropriate functions for that. However, these seem to discard 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 preserving this typing information?

like image 528
Julian B Avatar asked Dec 10 '15 12:12

Julian B


People also ask

How do you deep copy an object in TypeScript?

To create a deep copy of an object in TypeScript, install and use the lodash. clonedeep package. The cloneDeep method recursively clones a value and returns the result. The cloneDeep method returns an object of the correct type.

What is the most efficient way to deep clone an object in JavaScript?

Using the Json. Among the above mentioned three ways, for an object to be deep cloned, JSON. stringify() and JSON. parse() functions are used. The parse() method accepts a JSON String as a parameter and creates a JavaScript object accordingly.

How do you avoid reference copy in TypeScript?

So everyone needs to copy an object into another variable but we don't need to reference it, we need to create a new object from the copying object. So, on JavaScript & TypeScript languages, we have the option to do that in multiple ways. But we have used the “newObject = {… Object}” thing commonly in typescript.

Can we achieve deep cloning of an object by serialization?

To achieve a deep copy, we can serialize an object and then deserialize it to a new object.


2 Answers

Typescript isn't discarding type information here. 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 the arguments we don't care about, it takes a T as input, and spits out a T as output. So Typescript isn't losing any type information; it considers the output of cloneDeep to be the same type as the input.

You should be able to verify this via your editor: assuming you have some editor that lets you inspect the type of variables or autocompletes methods (which I'd highly recommend, if you don't).


Why then is the typeof not working as you expect? It's because the Typescript type information doesn't carry over to runtime. instanceof is a native JS operator, which Typescript doesn't change the behavior of, which 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 that b instanceof A is false is that instanceof is checking against constructors: x instanceof A returns true if the function A is a constructor somewhere in x's prototype chain (see the MDN documentation on instanceof). Lodash, however, doesn't use constructors when it clones objects. It can't. (How would it know what arguments to pass?) It creates a plain JS object that has all the methods of the cloned object, but doesn't reproduce it's prototype chain.

Lodash's clone (and most of lodash's methods, really) is best used when dealing with raw JS Objects. If you're using it in conjunction with constructors and instanceof checking things get a bit murky.


One solution here is to avoid the instanceof checking, and do something akin to duck typing; don't check that the object's constructor is a particular function, but check that the object has the properties that you expect it to.

Another solution is, as suggested in the comments, implement a clone method on your class itself, which wouldn't use lodash.

class A() {     clone() {         var cloned = new A(); //pass appropriate constructor args         //make other necessary changes to make the state match         return cloned;     } } 
like image 70
Retsam Avatar answered Oct 13 '22 08:10

Retsam


You can use Lodash#cloneDeep utility. Example of use :

import * as _ from "lodash";

...

{
    this.cloned = _.cloneDeep(data);
}
like image 40
Radouane ROUFID Avatar answered Oct 13 '22 09:10

Radouane ROUFID