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
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
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.
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.
let b = Object.assign({}, a);
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)));
}
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
.
TypeScript/JavaScript has its own operator for shallow cloning:
let shallowClone = { ...original };
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
For serializable deep clone, with Type Information is,
export function clone<T>(a: T): T {
return JSON.parse(JSON.stringify(a));
}
JSON.stringify
clone
. :)
undefined
value. See my comment on the similar answer above: stackoverflow.com/questions/28150967/typescript-cloning-object/…
Add "lodash.clonedeep": "^4.5.0"
to your package.json
. Then use like this:
import * as _ from 'lodash';
...
const copy = _.cloneDeep(original)
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)
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)
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!
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.
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;
}
cloneObj[attribut] = this.clone();
? or you mean cloneObj[attribut] = this[attribut].clone();
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.
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;
};
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.
You could use destructuring assignment with spread syntax :
var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};
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
// compile time error
for UncopiableUser
is always nice, but how well does it apply to recursive function solutions?
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
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.");
}
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)
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}
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
undefined
value. See my comment on the similar answer above: stackoverflow.com/questions/28150967/typescript-cloning-object/…
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
How about good old jQuery?! Here is deep clone:
var clone = $.extend(true, {}, sourceObject);
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;
}
}
}
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
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;
}
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()));
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")
Success story sharing
cloned instanceof MyClass === true
?