Advertisement
Advertisement


typescript - cloning object


Question

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

2015/01/26
1
195
1/26/2015 1:25:23 PM

Accepted Answer

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

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 difference 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
2020/06/13
263
6/13/2020 11:16:00 AM


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)));
}
2017/03/13

TypeScript/JavaScript has its own operator for shallow cloning:

let shallowClone = { ...original };
2020/06/29

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

2017/03/02

For serializable deep clone, with Type Information is,

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

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)
2019/01/01