typescript - cloning object

JavascriptTypescript

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

  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.

Solution 3 - Javascript

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

Solution 4 - Javascript

TypeScript/JavaScript has its own operator for shallow cloning:

let shallowClone = { ...original };

Solution 5 - Javascript

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

Solution 6 - Javascript

For serializable deep clone, with Type Information is,

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

Solution 7 - Javascript

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

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

Solution 8 - Javascript

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)

Solution 9 - Javascript

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.

Solution 10 - Javascript

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

Solution 11 - Javascript

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.

Solution 12 - Javascript

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

Solution 13 - Javascript

You could use destructuring assignment with spread syntax :

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

Solution 14 - Javascript

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

Solution 15 - Javascript

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    

Solution 16 - Javascript

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

Solution 17 - Javascript

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)

Solution 18 - Javascript

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}

Solution 19 - Javascript

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

Solution 20 - Javascript

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

Solution 21 - Javascript

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

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

Solution 22 - Javascript

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

Solution 23 - Javascript

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

Solution 24 - Javascript

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

Solution 25 - Javascript

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

Solution 26 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid LabergeView Question on Stackoverflow
Solution 1 - JavascriptFentonView Answer on Stackoverflow
Solution 2 - JavascriptMaciej SikoraView Answer on Stackoverflow
Solution 3 - JavascriptLarsView Answer on Stackoverflow
Solution 4 - JavascriptLuca C.View Answer on Stackoverflow
Solution 5 - JavascriptHomerView Answer on Stackoverflow
Solution 6 - JavascriptPolvView Answer on Stackoverflow
Solution 7 - Javascriptuser2878850View Answer on Stackoverflow
Solution 8 - JavascriptMuhammad AliView Answer on Stackoverflow
Solution 9 - JavascriptDecade MoonView Answer on Stackoverflow
Solution 10 - JavascriptpablorskView Answer on Stackoverflow
Solution 11 - JavascriptMauricio Gracia GutierrezView Answer on Stackoverflow
Solution 12 - JavascriptmarckassayView Answer on Stackoverflow
Solution 13 - JavascriptSOUVIK SAHAView Answer on Stackoverflow
Solution 14 - JavascriptValeriy KatkovView Answer on Stackoverflow
Solution 15 - JavascriptTimur OsadchiyView Answer on Stackoverflow
Solution 16 - Javascriptmaty jimenezView Answer on Stackoverflow
Solution 17 - JavascriptZiiMakcView Answer on Stackoverflow
Solution 18 - JavascriptblidView Answer on Stackoverflow
Solution 19 - JavascriptFerhatosView Answer on Stackoverflow
Solution 20 - JavascriptBernoulli ITView Answer on Stackoverflow
Solution 21 - JavascriptalehroView Answer on Stackoverflow
Solution 22 - JavascriptpatrickbadleyView Answer on Stackoverflow
Solution 23 - JavascriptCvassendView Answer on Stackoverflow
Solution 24 - Javascriptuser1931270View Answer on Stackoverflow
Solution 25 - JavascriptPradeet SwamyView Answer on Stackoverflow
Solution 26 - JavascriptLosManosView Answer on Stackoverflow