Deep clone in TypeScript (preserving types)

40,183

Solution 1

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;
    }
}

Solution 2

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 cloning clone() function:

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] [Complete source code]

Solution 3

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

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

Solution 4

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

import * as _ from "lodash";

...

{
    this.cloned = _.cloneDeep(data);
}
Share:
40,183

Related videos on Youtube

Julian B
Author by

Julian B

Updated on July 27, 2022

Comments

  • Julian B
    Julian B almost 2 years

    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?

    • YOU
      YOU over 8 years
      something like b: typeof a ?
    • Ruan Mendes
      Ruan Mendes over 8 years
      Automatically? No. You'd need to implement a clone method on the object and its nested objects.
    • Bruno Grieder
      Bruno Grieder over 8 years
      Preserving Typescript types and preserving SomeClass.prototype is not the same thing. See @Retsam answer and developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. Typescript types are erased at runtime.
    • Retsam
      Retsam over 8 years
      @BrunoGrieder Good call on linking the instanceof MDN documentation; I've edited that into my answer.
    • AsGoodAsItGets
      AsGoodAsItGets over 4 years
      I'm running the same test today (November 2019) with lodash 4.17.15 and it seems that the test is successful, i.e. instanceof returns true in both cases. Did something change in the meantime?
    • Farshad Bayat
      Farshad Bayat almost 4 years
      best solution and easy is here: stackoverflow.com/a/62969704/3913394
  • MonsieurDart
    MonsieurDart almost 5 years
    Caution with this: the date objects will be transformed into a string. I.e. its date type will not be kept.
  • krillgar
    krillgar over 4 years
    This answer, especially the section on TS types getting lost when executing in the browser, needs far more upvotes. Too many people don't understand that distinction.
  • Arcsector
    Arcsector about 3 years
    The usage of _.cloneDeep() as opposed to just the cloneDeep() function on it's own is what makes this answer great.
  • TamusJRoyce
    TamusJRoyce almost 3 years
    npmjs.com/package/lodash.clonedeep - there is also clonedeep stand-alone
  • TamusJRoyce
    TamusJRoyce over 2 years
    This is also not consistent in how it works across runtimes and browser environments