ChatGPT解决这个技术问题 Extra ChatGPT

typescript - cloning object

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


F
Fenton

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

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.
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...
I am getting the following error: "ERROR TypeError: this.constructor(...) is not a constructor"
Did you just make a public example out of that customer?
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?
B
BuZZ-dEE

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.

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.

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.


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.
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);
@KenRimple You are in 100% right, I added some more information.
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: developer.mozilla.org/en/docs/Web/JavaScript/Reference/… ('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.
L
Lars

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

This is ok, but you should keep in mind you'll lose prototype information and all types not supported in json when serialize/parse.
Also this seems less efficient comparing to the deepCopy function provided above.
I have this error: "Converting circular structure to JSON" when I use "(JSON.parse(JSON.stringify(objectToCopy)));"
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.
P
Pang

TypeScript/JavaScript has its own operator for shallow cloning:

let shallowClone = { ...original };

H
Homer

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


Note: this will create a shallow copy
P
Polv

For serializable deep clone, with Type Information is,

export function clone<T>(a: T): T {
  return JSON.parse(JSON.stringify(a));
}

This can change the order of the props. Just a warning for some people. Also it does not handle dates properly.
This can change the order of the props -- might try npmjs.com/package/es6-json-stable-stringify instead of JSON.stringify
@Polv, if someone is relying on the order of the keys in an object, I think they have bigger problem than clone. :)
This solution can miss keys with undefined value. See my comment on the similar answer above: stackoverflow.com/questions/28150967/typescript-cloning-object/…
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 -- stackoverflow.com/questions/122102/…
u
user2878850

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

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

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 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.)
M
Muhammad Ali

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)

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: repl.it/repls/CompetitivePreemptiveKeygen)
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 So how to ensure a deep copy?
any other solution in this question, which is doing copy-by-value recursively (e.g. stackoverflow.com/a/53025968 by marckassay) ensure that, since there is no reference to the source object being maintained in the target object.
D
Decade Moon

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.


D
Dylan Watson

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

Is correctcloneObj[attribut] = this.clone();? or you mean cloneObj[attribut] = this[attribut].clone();
M
Mauricio Gracia Gutierrez

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 constructor.


m
marckassay

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

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.
A
Augustin R

You could use destructuring assignment with spread syntax :

var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};

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.
V
Valeriy Katkov

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


// compile time error for UncopiableUser is always nice, but how well does it apply to recursive function solutions?
T
Timur Osadchiy

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    

K
Keith Stein

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.");
    }

Z
ZiiMakc

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)

b
blid

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}


F
Ferhatos

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


Can miss keys with undefined value. See my comment on the similar answer above: stackoverflow.com/questions/28150967/typescript-cloning-object/…
B
Bernoulli IT

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


a
alehro

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

var clone = $.extend(true, {}, sourceObject);

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.
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.
p
patrickbadley

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

C
Cvassend

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


u
user1931270

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

P
Pradeet Swamy
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?
L
LosManos

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")


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.