Coder Perfect

typescript – cloning object

Problem

I have a super class (Entity) that is the parent of numerous subclasses (Customer, Product, ProductCategory…)

In Typescript, I’d like to clone dynamically an object that has multiple sub-objects.

Consider the following scenario: a customer with many products and a ProductCategory.

var cust:Customer  = new Customer ();

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

I built a function in Entity to clone the entire object tree.

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

When the new is converted to javascript, the following error occurs: Cannot use ‘new’ with an expression whose type lacks a call or construct signature, according to error TS2351.

Despite the fact that the script works, I’d like to remove the transpiled mistake.

Asked by David Laberge

Solution #1

You can notify the compiler that you know better by using a type assertion:

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

Keep in mind that rather than being completely dynamic, it’s occasionally best to write your own mapping. There are, however, a few “cloning” techniques that can be used to create different results.

For the rest of the examples, I’ll use the following code:

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

Yes, it has properties. No methods were used. No deep copy

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

Methods: Inherited Properties: Inherited Shallow Inherited: Deep Copy (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: Make a deep copy

Yes, it has properties. No methods were used. Yes, there is deep copy.

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

Answered by Fenton

Solution #2

1.Use spread operator

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

The spread operator spreads all fields from obj1 across obj2. You’ll get a new object with a new reference and the same fields as the original.

Remember that this is a shallow copy, which means that if the object is nested, the nested composite params will be present in the new object with the same reference.

2.Object.assign()

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

Object.assign creates a true clone of the object with only its own attributes, therefore properties from the prototype will not be present in the replicated object. It’s also a sloppy copy.

3.Object.create()

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

Object.create does not perform true cloning; instead, it creates an object from a prototype. Because primary type attributes are not assigned by reference, it should be used if the object needs to clone them.

Any functions stated in prototype will be available in our newly generated object, which is one of the benefits of Object.create.

A few thoughts about shallow copy

Shallow copy duplicates all fields of the original object, but it also means that if the original object had composite type fields (objects, arrays, and so on), those fields are duplicated with the same references in the new object. The original object’s field will be reflected in the new object.

It may appear to be a trap, but situations when the entire complex object must be cloned are uncommon. Shallow copy will reuse the majority of memory, making it much less expensive than deep copy.

Deep copy

Deep copy can benefit from the use of the spread operator.

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

The code above made a deep copy of obj1. The “complex” composite field was likewise copied into obj2. The copy will not be reflected in the mutation field “complex.”

Answered by Maciej Sikora

Solution #3

Try this:

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

It’s a good answer unless you’re dealing with very huge objects or your object has properties that aren’t serializable.

You could use a copy function in the class you wish to make copies from to keep type safety:

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

Alternatively, in a static manner:

static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}

Answered by Lars

Solution #4

For shallow cloning, TypeScript/JavaScript offers its own operator:

let shallowClone = { ...original };

Answered by Luca C.

Solution #5

With Type Information, a serializable deep clone is possible.

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

Answered by Polv

Post is based on https://stackoverflow.com/questions/28150967/typescript-cloning-object