358

I have a super class that is the parent (Entity) for many subclass (Customer, Product, ProductCategory...)

I'm looking to clone dynamically an object that contains different sub objects in Typescript.

In example : a Customer that has different Product who has a ProductCategory

var cust:Customer  = new Customer ();

cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));

In order to clone the whole tree of object I created a function in Entity

public clone():any {
    var cloneObj = new this.constructor();
    for (var attribut in this) {
        if(typeof this[attribut] === "object"){
           cloneObj[attribut] = this.clone();
        } else {
           cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

The new rises the following error when it is transpiled to javascript: error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

Although the script works, I would like to get rid of the transpiled error

David Laberge
  • 15,435
  • 14
  • 53
  • 83

27 Answers27

466

Solving The Specific Issue

You can use a type assertion to tell the compiler that you know better:

public clone(): any {
    var cloneObj = new (this.constructor() as any);
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this[attribut].clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

Cloning

As of 2022, there is a proposal to allow structuredClone to deep copy many types.

const copy = structuredClone(value)

There are some limitations on what kind of thing you can use this on.

Bear in mind that sometimes it is better to write your own mapping - rather than being totally dynamic. However, there are a few "cloning" tricks you can use that give you different effects.

I will use the following code for all the subsequent examples:

class Example {
  constructor(public type: string) {

  }
}

class Customer {
  constructor(public name: string, public example: Example) {

  }

  greet() {
    return 'Hello ' + this.name;
  }
}

var customer = new Customer('David', new Example('DavidType'));

Option 1: Spread

Properties: Yes
Methods: No
Deep Copy: No

var clone = { ...customer };

alert(clone.name + ' ' + clone.example.type); // David DavidType
//alert(clone.greet()); // Not OK

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Option 2: Object.assign

Properties: Yes
Methods: No
Deep Copy: No

var clone = Object.assign({}, customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // Not OK, although compiler won't spot it

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Option 3: Object.create

Properties: Inherited
Methods: Inherited
Deep Copy: Shallow Inherited (deep changes affect both original and clone)

var clone = Object.create(customer);
    
alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK

customer.name = 'Misha';
customer.example = new Example("MishaType");

// clone sees changes to original 
alert(clone.name + ' ' + clone.example.type); // Misha MishaType

clone.name = 'Steve';
clone.example.type = 'SteveType';

// original sees changes to clone
alert(customer.name + ' ' + customer.example.type); // Misha SteveType

Option 4: Deep Copy Function

Properties: Yes
Methods: No
Deep Copy: Yes

function deepCopy(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = deepCopy(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

var clone = deepCopy(customer) as Customer;

alert(clone.name + ' ' + clone.example.type); // David DavidType
// alert(clone.greet()); // Not OK - not really a customer

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David DavidType
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • Close, the transpile stopped complainning with typescript 1.3, but once in javascript it would throw error. Typescript 1.4.1, won't let it go. – David Laberge Feb 05 '15 at 18:16
  • You could use generics to get back a result of the same type you are cloning. – robmcm Nov 29 '16 at 12:36
  • 1
    Would you be abel to clarify how do you exactly use this? I included as a method of my object and then got an error saying is not a function... – megalucio Apr 26 '17 at 00:35
  • 1
    I am getting the following error: "ERROR TypeError: this.constructor(...) is not a constructor" – michali Jun 11 '17 at 07:31
  • 4
    Did you just make a public example out of that customer? – Blair Connolly Jul 30 '19 at 17:53
  • For me, "Option 3: Object.create" was the trick to get properties to copy over to the new object. – Vince I Oct 30 '19 at 19:17
  • 1
    Can someone TL;DR for me which which of the solutions given in all the answers preserve the OO type of the clone, i.e. `cloned instanceof MyClass === true`? – Szczepan Hołyszewski Jan 25 '20 at 06:52
  • @VinceI The info for `Option 3: Object.create` is **misleading. It does *not* produce a copy.** "The Object.create() method creates a new object, using an existing object as the prototype of the newly created object." Sure, this *behaves* ALMOST as if it were a copy, but not truly: If you change the prototype afterwards, the "copy" will inherit this changes! This is perfect if you want one object to serve as the source of default values for another object, but not if you want an independent copy. – Inigo Apr 26 '20 at 16:00
  • @Fenton [I had proposed changes to this answer to reflect my comment above](https://stackoverflow.com/review/suggested-edits/25491504) but the reviewers misunderstood it (I think) and rejected it. Can you as the author of the answer approve it? If necessary I can resubmit. Let me know. – Inigo Apr 26 '20 at 16:02
  • the option 4 gives me the error: `Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'.ts(7053) ` in here `if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);` Any way to solve it? Thank you – Javier Apr 29 '20 at 09:30
  • Does 4th one handle cyclic references? I'm getting `Maximum call stack size exceeded`. – Jeppe Aug 04 '22 at 14:19
  • Option 4 looks vulnerable. MInor issue: the first condition is dealing with nullish values and 4 instead of 3 types: number, boolean, string and function. Functions are referenced instead of cloned. Functions can have properties themselves and those aren't deeply cloned. The last condition is redundant and the exception shouldn't be ever thrown, for only objects get passed that first condition. Eventually, the deep cloning for an object is vulnerable to prototype pollution attack. And I wonder how well instances of Map and Set are cloned. Not to mention the loss of constructor relationship. – Thomas Urban Jun 12 '23 at 06:35
272
  1. Use spread operator ...

     const obj1 = { param: "value" };
     const obj2 = { ...obj1 };
    

Spread operator takes all fields from obj1 and spread them over obj2. In the result you get new object with new reference and the same fields as original one.

Remember that it is shallow copy, it means that if object is nested then its nested composite params will exists in the new object by the same reference.

  1. Object.assign()

     const obj1={ param: "value" };
     const obj2:any = Object.assign({}, obj1);
    

Object.assign create real copy, but only own properties, so properties in prototype will not exist in copied object. It is also shallow copy.


  1. Object.create()

     const obj1={ param: "value" };
     const obj2:any = Object.create(obj1);
    

Object.create is not doing real cloning, it is creating object from prototype. So use it if the object should clone primary type properties, because primary type properties assignment is not done by reference.

Pluses of Object.create are that any functions declared in prototype will be available in our newly created object.


Few things about shallow copy

Shallow copy puts into new object all fields of the old one, but it also means that if original object has composite type fields (object, arrays etc.) then those fields are put in new object with the same references. Mutation such field in original object will be reflected in new object.

It maybe looks like a pitfall, but really situation when the whole complex object needs to be copied is rare. Shallow copy will re-use most of memory which means that is very cheap in comparison to deep copy.


Deep copy

Spread operator can be handy for deep copy.

const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};

Above code created deep copy of obj1. Composite field "complex" was also copied into obj2. Mutation field "complex" will not reflect the copy.

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
  • 8
    I don't think that's completely correct. `Object.create(obj1)` creates a new object and assigns obj1 as the prototype. None of the fields in obj1 are copied or cloned. So changes on obj1 without modifying obj2 will be seen, since it essentially has no properties. If you modify obj2 first, the prototype will not be seen for the field you define since obj2's field with the name is closer in the hierarchy. – Ken Rimple Sep 16 '16 at 19:08
  • 3
    You'll also see ES2015 and typescript developers doing this instead, which creates an object from the 1st parameter (in my case an empty one) and copies the properties from the second and subsequent params): `let b = Object.assign({}, a);` – Ken Rimple Sep 16 '16 at 19:12
  • @KenRimple You are in 100% right, I added some more information. – Maciej Sikora Sep 19 '16 at 11:47
  • maybe be helpful => https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign – Emmanuel Touzery Nov 16 '16 at 07:42
  • 5
    Object.assign will create issues for deep objects. For example {name: 'x', values: ['a','b','c']}. After using Object.assign to clone, both objects share the values array so updating one affects the other. See: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign ('Warning for Deep Clone' section). It says: For deep cloning, we need to use other alternatives. This is because Object.assign() copies the property reference when the property being assigned is an object. – Meir Dec 08 '16 at 08:42
82

Try this:

let copy = (JSON.parse(JSON.stringify(objectToCopy)));

It is a good solution until you are using very large objects or your object has unserializable properties.

In order to preserve type safety you could use a copy function in the class you want to make copies from:

getCopy(): YourClassName{
    return (JSON.parse(JSON.stringify(this)));
}

or in a static way:

static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}
Lars
  • 2,315
  • 2
  • 24
  • 29
  • 9
    This is ok, but you should keep in mind you'll lose prototype information and all types not supported in json when serialize/parse. – Stanislav E. Govorov Mar 27 '17 at 09:04
  • 1
    Also this seems less efficient comparing to the deepCopy function provided [above](https://stackoverflow.com/a/28152032/1730846). – Mojtaba Nov 08 '18 at 11:37
  • I have this error: "Converting circular structure to JSON" when I use "(JSON.parse(JSON.stringify(objectToCopy)));" – Cedric Arnould Mar 06 '19 at 15:02
  • 1
    Only works in 98% of the cases. Can lead to missing keys with `undefined` value, at least. if `objectToCopy = { x : undefined};` then after running your code `Object.keys(objectToCopy).length` is `1`, while `Object.keys(copy).length` is `0`. – Aidin Feb 12 '20 at 23:29
62

TypeScript/JavaScript has its own operator for shallow cloning:

let shallowClone = { ...original };
Pang
  • 9,564
  • 146
  • 81
  • 122
Luca C.
  • 11,714
  • 1
  • 86
  • 77
  • 2
    Anyone using this please be aware that if there are nested objects the references will be preserved hence the "Shallow". – lk404 Jan 08 '23 at 17:21
19

It's easy to get a shallow copy with "Object Spread" introduced in TypeScript 2.1

this TypeScript: let copy = { ...original };

produces this JavaScript:

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var copy = __assign({}, original);

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Homer
  • 7,594
  • 14
  • 69
  • 109
18

For serializable deep clone, with Type Information is,

export function clone<T>(a: T): T {
  return JSON.parse(JSON.stringify(a));
}
Polv
  • 1,918
  • 1
  • 20
  • 31
  • 2
    This can change the order of the props. Just a warning for some people. Also it does not handle dates properly. – Pangamma Oct 30 '19 at 23:19
  • This can change the order of the props -- might try https://www.npmjs.com/package/es6-json-stable-stringify instead of `JSON.stringify` – Polv Oct 31 '19 at 00:52
  • 3
    @Polv, if someone is relying on the order of the keys in an object, I think they have bigger problem than `clone`. :) – Aidin Feb 12 '20 at 23:46
  • This solution can miss keys with `undefined` value. See my comment on the similar answer above: https://stackoverflow.com/questions/28150967/typescript-cloning-object/38874807#comment106476826_42758108 – Aidin Feb 12 '20 at 23:46
  • I did explicitly say "serializable" though. Also, it does depend on the use case, but I would always happily throw away undefined (which I know, is impossible in Arrays). For Dates and RegExps, or more than that (e.g. most classes, most functions), I recommend recursive functions -- https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript?answertab=votes#tab-top – Polv Aug 02 '20 at 16:53
11

Add "lodash.clonedeep": "^4.5.0" to your package.json. Then use like this:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)
user2878850
  • 2,446
  • 1
  • 18
  • 22
  • I just wonder if it is OK to use a library, if you don't really know / understand the implementations / implications? (The implementation for cloneDeep is https://github.com/lodash/lodash/blob/master/.internal/baseClone.js) I think recursive functions that touch non-enumerable properties are amongst the best solutions. (Somewhere in [this QA](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript?answertab=votes#tab-top).) – Polv Aug 02 '20 at 17:09
8

My take on it:

Object.assign(...) only copies properties and we lose the prototype and methods.

Object.create(...) is not copying properties for me and just creating a prototype.

What worked for me is creating a prototype using Object.create(...) and copying properties to it using Object.assign(...):

So for an object foo, make clone like this:

Object.assign(Object.create(foo), foo)
Muhammad Ali
  • 599
  • 1
  • 7
  • 15
  • There is a very subtle thing going on here. You are actually making `foo` be the prototypical parent of the `clonedFoo` (new object). While this might sound ok, you should keep in mind that a missing property will be looked up in the prototype chain, so `const a = { x: 8 }; const c = Object.assign(Object.create(a), a); delete c.x; console.log(c.x);` prints out 8, while should be `undefined`! (REPL link: https://repl.it/repls/CompetitivePreemptiveKeygen) – Aidin Feb 12 '20 at 23:43
  • Additionally, if you later add a property to `foo`, it will automatically show up for `clonedFoo`! e.g. `foo.y = 9; console.log(clonedFoo.y)` will print out `9` instead of `undefined`. It's very likely that it's not what you are asking for! – Aidin Feb 12 '20 at 23:51
  • @Aidin So how to ensure a deep copy? – Muhammad Ali Jul 13 '20 at 21:21
  • any other solution in this question, which is doing copy-by-value recursively (e.g. https://stackoverflow.com/a/53025968 by marckassay) ensure that, since there is no reference to the source object being maintained in the target object. – Aidin Jul 14 '20 at 23:51
7

You can also have something like this:

class Entity {
    id: number;

    constructor(id: number) {
        this.id = id;
    }

    clone(): this {
        return new (this.constructor as typeof Entity)(this.id) as this;
    }
}

class Customer extends Entity {
    name: string;

    constructor(id: number, name: string) {
        super(id);
        this.name = name;
    }

    clone(): this {
        return new (this.constructor as typeof Customer)(this.id, this.name) as this;
    }
}

Just make sure that you override the clone method in all Entity subclasses otherwise you'll end up with partial clones.

The return type of this will always match the type of the instance.

Decade Moon
  • 32,968
  • 8
  • 81
  • 101
6

If you want to also copy the methods, not just the data, follow this approach

let copy = new BaseLayer() ;
Object.assign(copy, origin);
copy.x = 8 ; //will not affect the origin object

Just change BaseLayer to the name of your class.

Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
  • 1
    The OP topic only says "cloning object", though the OP question is entirely about classes. According to the detailed question, with classes, yes, this answer seems correct (in addition to others). However, if we just go by the phrase "cloning object", and the object is defined by an Interface and not a Class, then this solution won't work - whereas others involving spread or structuredClone() do work (2022). – TonyG Dec 16 '22 at 23:45
4

If you get this error:

TypeError: this.constructor(...) is not a function

This is the correct script:

public clone(): any {
    var cloneObj = new (<any>this.constructor)(); // line fixed
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this[attribut].clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}
Dylan Watson
  • 2,283
  • 2
  • 20
  • 38
pablorsk
  • 3,861
  • 1
  • 32
  • 37
  • 4
    Is correct`cloneObj[attribut] = this.clone();`? or you mean `cloneObj[attribut] = this[attribut].clone();` – Serginho Jan 16 '17 at 15:58
4

Since TypeScript 3.7 is released, recursive type aliases are now supported and it allows us to define a type safe deepCopy() function:

// DeepCopy type can be easily extended by other types,
// like Set & Map if the implementation supports them.
type DeepCopy<T> =
    T extends undefined | null | boolean | string | number ? T :
    T extends Function | Set<any> | Map<any, any> ? unknown :
    T extends ReadonlyArray<infer U> ? Array<DeepCopy<U>> :
    { [K in keyof T]: DeepCopy<T[K]> };

function deepCopy<T>(obj: T): DeepCopy<T> {
    // implementation doesn't matter, just use the simplest
    return JSON.parse(JSON.stringify(obj));
}

interface User {
    name: string,
    achievements: readonly string[],
    extras?: {
        city: string;
    }
}

type UncopiableUser = User & {
    delete: () => void
};

declare const user: User;
const userCopy: User = deepCopy(user); // no errors

declare const uncopiableUser: UncopiableUser;
const uncopiableUserCopy: UncopiableUser = deepCopy(uncopiableUser); // compile time error

Playground

Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123
  • `// compile time error` for `UncopiableUser` is always nice, but how well does it apply to recursive function solutions? – Polv Aug 02 '20 at 16:59
4

Here is deepCopy implementation in TypeScript (no any is included in the code):

const deepCopy = <T, U = T extends Array<infer V> ? V : never>(source: T ): T => {
  if (Array.isArray(source)) {
    return source.map(item => (deepCopy(item))) as T & U[]
  }
  if (source instanceof Date) {
    return new Date(source.getTime()) as T & Date
  }
  if (source && typeof source === 'object') {
    return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
      Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
      o[prop] = deepCopy(source[prop])
      return o
    }, Object.create(Object.getPrototypeOf(source)))
  }
  return source
}
3

Here is my mash-up! And here is a StackBlitz link to it. Its currently limited to only copying simple types and object types but could be modified easily I would think.

   let deepClone = <T>(source: T): { [k: string]: any } => {
      let results: { [k: string]: any } = {};
      for (let P in source) {
        if (typeof source[P] === 'object') {
          results[P] = deepClone(source[P]);
        } else {
          results[P] = source[P];
        }
      }
      return results;
    };
marckassay
  • 1,350
  • 1
  • 11
  • 19
  • 1
    Works pretty well as far as I can see. However, `typeof null` is also an object, so the query should be `if (source[P] !== null && typeof source[P] === 'object')` instead. Otherwise your null values will get turned into an empty object. – MortenMoulder May 09 '19 at 08:48
3

You could use destructuring assignment with spread syntax :

var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};
Augustin R
  • 7,089
  • 3
  • 26
  • 54
SOUVIK SAHA
  • 191
  • 2
  • 4
  • 2
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – leopal Feb 18 '20 at 14:03
2

Came across this problem myself and in the end wrote a small library cloneable-ts that provides an abstract class, which adds a clone method to any class extending it. The abstract class borrows the Deep Copy Function described in the accepted answer by Fenton only replacing copy = {}; with copy = Object.create(originalObj) to preserve the class of the original object. Here is an example of using the class.

import {Cloneable, CloneableArgs} from 'cloneable-ts';

// Interface that will be used as named arguments to initialize and clone an object
interface PersonArgs {
    readonly name: string;
    readonly age: number;
}

// Cloneable abstract class initializes the object with super method and adds the clone method
// CloneableArgs interface ensures that all properties defined in the argument interface are defined in class
class Person extends Cloneable<TestArgs>  implements CloneableArgs<PersonArgs> {
    readonly name: string;
    readonly age: number;

    constructor(args: TestArgs) {
        super(args);
    }
}

const a = new Person({name: 'Alice', age: 28});
const b = a.clone({name: 'Bob'})
a.name // Alice
b.name // Bob
b.age // 28

Or you could just use the Cloneable.clone helper method:

import {Cloneable} from 'cloneable-ts';

interface Person {
    readonly name: string;
    readonly age: number;
}

const a: Person = {name: 'Alice', age: 28};
const b = Cloneable.clone(a, {name: 'Bob'})
a.name // Alice
b.name // Bob
b.age // 28    
Timur Osadchiy
  • 5,699
  • 2
  • 26
  • 28
2

Here is a modern implementation that accounts for Set and Map too:

export function deepClone<T extends object>(value: T): T {
  if (typeof value !== 'object' || value === null) {
    return value;
  }

  if (value instanceof Set) {
    return new Set(Array.from(value, deepClone)) as T;
  }

  if (value instanceof Map) {
    return new Map(Array.from(value, ([k, v]) => [k, deepClone(v)])) as T;
  }

  if (value instanceof Date) {
    return new Date(value) as T;
  }

  if (value instanceof RegExp) {
    return new RegExp(value.source, value.flags) as T;
  }

  return Object.keys(value).reduce((acc, key) => {
    return Object.assign(acc, { [key]: deepClone(value[key]) });
  }, (Array.isArray(value) ? [] : {}) as T);
}

Trying it out:

deepClone({
  test1: { '1': 1, '2': {}, '3': [1, 2, 3] },
  test2: [1, 2, 3],
  test3: new Set([1, 2, [1, 2, 3]]),
  test4: new Map([['1', 1], ['2', 2], ['3', 3]])
});

test1:
  1: 1
  2: {}
  3: [1, 2, 3]

test2: Array(3)
  0: 1
  1: 2
  2: 3

test3: Set(3)
  0: 1
  1: 2
  2: [1, 2, 3]

test4: Map(3)
  0: {"1" => 1}
  1: {"2" => 2}
  2: {"3" => 3}

blid
  • 971
  • 13
  • 22
1

In typeScript I test with angular, and it's doing OK

deepCopy(obj) {


        var copy;

        // Handle the 3 simple types, and null or undefined
        if (null == obj || "object" != typeof obj) return obj;

        // Handle Date
        if (obj instanceof Date) {
            copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            copy = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                copy[i] = this.deepCopy(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = this.deepCopy(obj[attr]);
            }
            return copy;
        }

        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
Keith Stein
  • 6,235
  • 4
  • 17
  • 36
1

For deep cloning an object that can contain another objects, arrays and so on i use:

const clone = <T>(source: T): T => {
  if (source === null) return source

  if (source instanceof Date) return new Date(source.getTime()) as any

  if (source instanceof Array) return source.map((item: any) => clone<any>(item)) as any

  if (typeof source === 'object' && source !== {}) {
    const clonnedObj = { ...(source as { [key: string]: any }) } as { [key: string]: any }
    Object.keys(clonnedObj).forEach(prop => {
      clonnedObj[prop] = clone<any>(clonnedObj[prop])
    })

    return clonnedObj as T
  }

  return source
}

Use:

const obj = {a: [1,2], b: 's', c: () => { return 'h'; }, d: null, e: {a:['x'] }}
const objClone = clone(obj)
ZiiMakc
  • 31,187
  • 24
  • 65
  • 105
1

Supplementary for option 4 by @fenton, using angularJS it is rather simple to do a deep copy of either an object or array using the following code:

var deepCopy = angular.copy(objectOrArrayToBeCopied)

More documentation can be found here: https://docs.angularjs.org/api/ng/function/angular.copy

Cvassend
  • 126
  • 11
0

I ended up doing:

public clone(): any {
  const result = new (<any>this.constructor);

  // some deserialization code I hade in place already...
  // which deep copies all serialized properties of the
  // object graph
  // result.deserialize(this)

  // you could use any of the usggestions in the other answers to
  // copy over all the desired fields / properties

  return result;
}

Because:

var cloneObj = new (<any>this.constructor());

from @Fenton gave runtime errors.

Typescript version: 2.4.2

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
0

How about good old jQuery?! Here is deep clone:

var clone = $.extend(true, {}, sourceObject);
alehro
  • 2,198
  • 2
  • 25
  • 41
  • This question wasn't tagged JQuery nor was JQuery mentioned in the question. It also would be massive overhead to include JQuery in a project just to do a deep clone. – LewisM Apr 14 '19 at 17:41
  • That's fair enough, but the OP isn't about how to clone, it is about identifying an issue in the code he provided and you responded with the jQuery way of cloning without really answering the question. I'm not the one who downvoted you, but I believe that may be why you were downvoted. – LewisM Apr 16 '19 at 08:37
0

I took a stab at creating a generic copy/clone service that retains types for nested objects. Would love feedback if i'm doing something wrong, but it seems to work so far...

import { Injectable } from '@angular/core';

@Injectable()
export class CopyService {

  public deepCopy<T>(objectToClone: T): T {
    // If it's a simple type or null, just return it.
    if (typeof objectToClone === 'string' ||
      typeof objectToClone === 'number' ||
      typeof objectToClone === 'undefined' ||
      typeof objectToClone === 'symbol' ||
      typeof objectToClone === 'function' ||
      typeof objectToClone === 'boolean' ||
      objectToClone === null
    ) {
      return objectToClone;
    }

    // Otherwise, check if it has a constructor we can use to properly instantiate it...
    let ctor = Object.getPrototypeOf(objectToClone).constructor;
    if (ctor) {
      let clone = new ctor();

      // Once we've instantiated the correct type, assign the child properties with deep copies of the values
      Object.keys(objectToClone).forEach(key => {
        if (Array.isArray(objectToClone[key]))
          clone[key] = objectToClone[key].map(item => this.deepCopy(item));
        else
          clone[key] = this.deepCopy(objectToClone[key]);
      });

      if (JSON.stringify(objectToClone) !== JSON.stringify(clone))
        console.warn('object cloned, but doesnt match exactly...\nobject: ' + JSON.stringify(objectToClone) + "\nclone: " + JSON.stringify(clone))

      // return our cloned object...
      return clone;
    }
    else {
      //not sure this will ever get hit, but figured I'd have a catch call.
      console.log('deep copy found something it didnt know: ' + JSON.stringify(objectToClone));
      return objectToClone;
    }
  }
}
patrickbadley
  • 2,510
  • 2
  • 29
  • 30
0
function instantiateEmptyObject(obj: object): object {
    if (obj == null) { return {}; }

    const prototype = Object.getPrototypeOf(obj);
    if (!prototype) {
        return {};
    }

    return Object.create(prototype);
}

function quickCopy(src: object, dest: object): object {
    if (dest == null) { return dest; }

    return { ...src, ...dest };
}

quickCopy(src, instantiateEmptyObject(new Customer()));
  • This answer in its current state isn't that useful. Can you add more details on how to use this to solve the original problem? – phsource Aug 10 '20 at 18:40
0

I use the following when cloning. It handles most everything that I need and even copies the functions to the newly created object.

  public static clone<T>(value: any) : T {
    var o: any = <any>JSON.parse(JSON.stringify(value));
    var functions = (<String[]>Object.getOwnPropertyNames(Object.getPrototypeOf(value))).filter(a => a != 'constructor');
    for (var i = 0; i < functions.length; i++) {
      var name = functions[i].toString();
      o[name] = value[name];
    }
    return <T>o;
  }
-1

For a simple clone of the hole object's content, I simply stringify and parse the instance :

let cloneObject = JSON.parse(JSON.stringify(objectToClone))

Whereas I change data in objectToClone tree, there is no change in cloneObject. That was my requierement.

Hope it help

Ferhatos
  • 73
  • 2
  • 9
  • 1
    Can miss keys with `undefined` value. See my comment on the similar answer above: https://stackoverflow.com/questions/28150967/typescript-cloning-object/38874807#comment106476826_42758108 – Aidin Feb 12 '20 at 23:32
-2

If you already have the target object, so you don't want to create it anew (like if updating an array) you must copy the properties.
If have done it this way:

Object.keys(source).forEach((key) => {
    copy[key] = source[key]
})

Praise is due. (look at headline "version 2")

LosManos
  • 7,195
  • 6
  • 56
  • 107
  • Functions? Arrays? Date objects? Preservation of types? And of course what about objects? If the above function encounters any of the above types it will fail to deep clone. You will have copied the references to the same data. When they go to edit the child properties of the cloned object they will end up editing the original object as well. – Pangamma Oct 30 '19 at 23:19