web-dev-qa-db-fra.com

typescript - objet de clonage

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

96
David Laberge

Résoudre le problème spécifique

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

Clonage

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
126
Fenton

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.

127
Maciej Sikora

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

TypeScript/Javascript a son propre opérateur pour le clonage superficiel:

let shallowClone = { ...original };
20
Luca C.

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

14
Homer

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.

4
Decade Moon

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

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    
2
Tim Osadchiy

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)
2
Muhammad Ali

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

1
Ferhatos

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

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

Que diriez-vous du bon vieux jQuery?! Voici le clone profond:

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

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

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

0
Bernoulli IT

Ajoutez "lodash.clonedeep": "^4.5.0" à votre package.json. Ensuite, utilisez comme ceci:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)
0
user2878850