J'ai une super classe qui est le parent (Entity
) pour plusieurs sous-classes (Customer
, Product
, ProductCategory
...)
Je cherche à cloner dynamiquement un objet contenant différents sous-objets dans TypeScript.
Par exemple: un Customer
qui a un Product
différent qui a un ProductCategory
var cust:Customer = new Customer ();
cust.name = "someName";
cust.products.Push(new Product(someId1));
cust.products.Push(new Product(someId2));
Afin de cloner l'arbre entier de l'objet, j'ai créé une fonction dans 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;
}
La new
génère l'erreur suivante lorsqu'elle est transpilée en javascript: error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
Bien que le script fonctionne,je voudrais supprimer l’erreur transpilée
Vous pouvez utiliser une assertion de type pour indiquer au compilateur que vous connaissez mieux:
public clone(): any {
var cloneObj = new (<any>this.constructor());
for (var attribut in this) {
if (typeof this[attribut] === "object") {
cloneObj[attribut] = this.clone();
} else {
cloneObj[attribut] = this[attribut];
}
}
return cloneObj;
}
N'oubliez pas qu'il est parfois préférable d'écrire votre propre mapping, plutôt que d'être totalement dynamique. Cependant, il existe quelques astuces de "clonage" que vous pouvez utiliser pour vous donner des effets de différence.
Je vais utiliser le code suivant pour tous les exemples suivants:
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: propagation
Propriétés: oui
Méthodes: Non
Copie profonde: Non
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
Propriétés: oui
Méthodes: Non
Copie profonde: Non
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
Propriétés: oui
Méthodes: Oui
Copie profonde: Non
var clone = Object.create(customer);
alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK
clone.name = 'Steve';
clone.example.type = 'SteveType';
alert(customer.name + ' ' + customer.example.type); // David SteveType
Option 4: fonction de copie en profondeur
Propriétés: oui
Méthodes: Non
Copie profonde: Oui
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 = <Customer>deepCopy(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
Opérateur de propagation 1.Use
const obj1 = { param: "value" };
const obj2 = { ...obj1 };
L'opérateur Spread prend tous les champs de obj1 et les répartit sur obj2. Dans le résultat, vous obtenez un nouvel objet avec une nouvelle référence et les mêmes champs que ceux d'origine.
N'oubliez pas qu'il s'agit d'une copie superficielle, cela signifie que si l'objet est imbriqué, ses paramètres composites imbriqués existent dans le nouvel objet avec la même référence.
2.Objet.assign ()
const obj1={ param: "value" };
const obj2:any = Object.assign({}, obj1);
Object.assign crée une copie réelle, mais uniquement des propriétés propres; les propriétés du prototype n'existeront donc pas dans l'objet copié. C'est aussi une copie superficielle.
3.Objet.create ()
const obj1={ param: "value" };
const obj2:any = Object.create(obj1);
Object.create
ne fait pas de vrai clonage , il crée un objet à partir d'un prototype. Donc, utilisez-le si l'objet doit cloner les propriétés de type primaire, car l'attribution des propriétés de type primaire n'est pas faite par référence.
Les atouts de Object.create sont que toutes les fonctions déclarées dans prototype seront disponibles dans notre objet nouvellement créé.
Peu de choses sur la copie superficielle
Une copie superficielle place dans un nouvel objet tous les champs de l'ancien, mais cela signifie également que si l'objet d'origine a des champs de type composite (objet, tableaux, etc.), ces champs sont placés dans un nouvel objet avec les mêmes références. La mutation d'un tel champ dans l'objet original sera reflétée dans le nouvel objet.
Cela ressemble peut-être à un écueil, mais la situation dans laquelle un objet complexe doit être copié est rare. Une copie superficielle réutilisera la majeure partie de la mémoire, ce qui signifie qu'elle est très économique par rapport à la copie profonde.
Copie profonde
L'opérateur Spread peut être pratique pour la copie en profondeur.
const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};
Le code ci-dessus a créé une copie complète de obj1. Le champ "complexe" composé a également été copié dans obj2. Le champ de mutation "complexe" ne reflétera pas la copie.
Essaye ça:
let copy = (JSON.parse(JSON.stringify(objectToCopy)));
C'est une bonne solution jusqu'à ce que vous utilisiez de très gros objets ou que votre objet ait des propriétés non sérialisables.
Afin de préserver la sécurité du type, vous pouvez utiliser une fonction de copie dans la classe à partir de laquelle vous souhaitez effectuer des copies:
getCopy(): YourClassName{
return (JSON.parse(JSON.stringify(this)));
}
ou de manière statique:
static createCopy(objectToCopy: YourClassName): YourClassName{
return (JSON.parse(JSON.stringify(objectToCopy)));
}
TypeScript/Javascript a son propre opérateur pour le clonage superficiel:
let shallowClone = { ...original };
Il est facile d'obtenir une copie superficielle avec "Object Spread" introduit dans TypeScript 2.1.
ce TypeScript: let copy = { ...original };
produit ce 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
Vous pouvez aussi avoir quelque chose comme ça:
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;
}
}
Assurez-vous simplement que vous substituez la méthode clone
à toutes les sous-classes Entity
, sinon vous obtiendrez des clones partiels.
Le type de retour de this
correspondra toujours au type de l'instance.
Pour un clone profond sérialisable, avec les informations de type est,
export function clone<T>(a: T): T {
return JSON.parse(JSON.stringify(a));
}
Je suis tombé sur ce problème moi-même et à la fin j'ai écrit une petite bibliothèque cloneable-ts qui fournit une classe abstraite, qui ajoute une méthode de clonage à toute classe l’étendant. La classe abstraite emprunte la fonction de copie en profondeur décrite dans la réponse acceptée par Fenton, en remplaçant uniquement copy = {};
par copy = Object.create(originalObj)
afin de préserver la classe de l'objet d'origine. Voici un exemple d'utilisation de la classe.
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
Ou vous pouvez simplement utiliser la méthode d'assistance Cloneable.clone
:
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
Mon point de vue:
Object.assign(...)
copie uniquement les propriétés et nous perdons le prototype et les méthodes.
Object.create(...)
ne copie pas les propriétés pour moi et crée simplement un prototype.
Ce qui a fonctionné pour moi, c’est de créer un prototype avec Object.create(...)
et d’y copier les propriétés en utilisant Object.assign(...)
:
Donc pour un objet foo
, faites un clone comme ceci:
Object.assign(Object.create(foo), foo)
Pour un simple clone du contenu de l'objet trou, il suffit de stringifier et d'analyser l'instance:
let cloneObject = JSON.parse(JSON.stringify(objectToClone))
Alors que je change les données dans l'arborescence objectToClone, cloneObject ne change pas. C'était mon requierement.
J'espère que ça aide
Si vous obtenez cette erreur:
TypeError: this.constructor(...) is not a function
C'est le bon 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.clone();
} else {
cloneObj[attribut] = this[attribut];
}
}
return cloneObj;
}
J'ai essayé de créer un service de copie/clone générique qui conserve les types d'objets imbriqués. J'adorerais les commentaires si je fais quelque chose de mal, mais cela semble fonctionner jusqu'à présent ...
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;
}
}
}
Que diriez-vous du bon vieux jQuery?! Voici le clone profond:
var clone = $.extend(true, {}, sourceObject);
Voici mon mash-up! Et voici un lien StackBlitz vers celui-ci. Il est actuellement limité à la copie de types simples et d'objets mais peut être modifié facilement, à mon avis.
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;
};
J'ai fini par faire:
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;
}
Parce que:
var cloneObj = new (<any>this.constructor());
de @Fenton a donné des erreurs d'exécution.
Version TypeScript: 2.4.2
Ajoutez "lodash.clonedeep": "^4.5.0"
à votre package.json
. Ensuite, utilisez comme ceci:
import * as _ from 'lodash';
...
const copy = _.cloneDeep(original)