typescript-オブジェクトの複製


186

私は、スーパークラスを持つ親(Entity多くのサブクラスのための)( 、、Customer ...)ProductProductCategory

Typescriptのさまざまなサブオブジェクトを含むオブジェクトを動的に複製しようとしています。

例:Customerを持っているProduct人が異なるProductCategory

var cust:Customer  = new Customer ();

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

オブジェクトのツリー全体を複製するために、 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;
}

newそれはJavaScriptにtranspiledされ、次のエラーを上昇します:error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

スクリプトは機能しますが、変換されたエラーを取り除きたいです

回答:


255

特定の問題を解決する

型アサーションを使用して、コンパイラにあなたがよりよく知っていることを伝えることができます。

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

クローニング

完全に動的であるよりも、独自のマッピングを作成する方がよい場合があることを覚えておいてください。ただし、効果を変えるために使用できるいくつかの「クローニング」トリックがあります。

以降のすべての例で次のコードを使用します。

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'));

オプション1:スプレッド

プロパティ:はい
メソッド:いいえ
ディープコピー:いいえ

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

オプション2:Object.assign

プロパティ:はい
メソッド:いいえ
ディープコピー:いいえ

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

オプション3:Object.create

プロパティ:継承された
メソッド:継承された
ディープコピー:浅い継承(深い変更はオリジナルとクローンの両方に影響します)

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

オプション4:ディープコピー機能

プロパティ:はい
メソッド:いいえ
ディープコピー:はい

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

閉じると、トランスパイルはtypescript 1.3で文句を言うのをやめましたが、JavaScriptで一度エラーをスローしました。Typescript 1.4.1、手放せません。
David Laberge、2015

1
これを正確にどのように使用するかを明確にすることはできますか?オブジェクトのメソッドとして含めたところ、関数ではないというエラーが表示されました...
megalucio

1
次のエラーが発生します:「ERROR TypeError:this.constructor(...)is not a constructor」
michali

3
その顧客から公の例を作っただけですか?
ブレアコノリー

1
誰かがTL; DRしてくれcloned instanceof MyClass === trueますか?すべての回答で提供されているソリューションのどれがクローンのOOタイプを保持しますか?
SzczepanHołyszewski1

177

1.スプレッド演算子を使用する

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

スプレッド演算子は、obj1からすべてのフィールドを取得し、それらをobj2に分散します。その結果、新しい参照と元のオブジェクトと同じフィールドを持つ新しいオブジェクトが得られます。

浅いコピーであることを忘れないでください。つまり、オブジェクトがネストされている場合、そのネストされた複合パラメーターは、同じ参照によって新しいオブジェクトに存在することになります。

2.Object.assign()

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

Object.assignは実際のコピーを作成しますが、独自のプロパティのみなので、プロトタイプのプロパティはコピーされたオブジェクトに存在しません。浅いコピーでもあります。


3.Object.create()

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

Object.create 本当のクローニングを行っていない、それはプロトタイプからオブジェクトを作成しています。したがって、プライマリタイププロパティの割り当ては参照によって行われないため、オブジェクトがプライマリタイププロパティのクローンを作成する場合に使用します。

Object.createのプラスは、プロトタイプで宣言されたすべての関数が、新しく作成されたオブジェクトで使用できることです。


浅いコピーに関するいくつかのこと

浅いコピーは、古いオブジェクトのすべてのフィールドを新しいオブジェクトに配置しますが、元のオブジェクトに複合型フィールド(オブジェクト、配列など)がある場合、それらのフィールドは同じ参照で新しいオブジェクトに配置されることも意味します。元のオブジェクトのこのようなフィールドの変異は、新しいオブジェクトに反映されます。

落とし穴のように見えるかもしれませんが、実際に複雑なオブジェクト全体をコピーする必要がある状況はまれです。浅いコピーはほとんどのメモリを再利用します。つまり、深いコピーと比較して非常に安価です。


深いコピー

スプレッドオペレーターは、ディープコピーに便利です。

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

上記のコードは、obj1のディープコピーを作成しました。複合フィールド「complex」もobj2にコピーされました。変異フィールド「complex」はコピーを反映しません。


8
それは完全に正しいとは思いません。Object.create(obj1)新しいオブジェクトを作成し、obj1をプロトタイプとして割り当てます。obj1のどのフィールドもコピーまたは複製されません。したがって、obj2は本質的にプロパティを持たないため、obj2を変更せずにobj1に変更が加えられます。最初にobj2を変更すると、定義したフィールドのプロトタイプは表示されません。これは、名前を持つobj2のフィールドが階層内でより近いためです。
Ken Rimple 2016

3
:あなたはまた)ES2015とtypescriptです開発者の代わりにこれをやって、(私の場合、空のいずれかで)第一パラメータからオブジェクトを作成し、コピー目以降のparamsからプロパティを参照してくださいよ let b = Object.assign({}, a);
ケンRimple

@KenRimple 100%正解です。さらに情報を追加しました。
Maciej Sikora


5
Object.assignは、深いオブジェクトの問題を作成します。たとえば、{名前: 'x'、値:['a'、 'b'、 'c']}。Object.assignを使用してクローンを作成すると、両方のオブジェクトが値の配列を共有するため、一方を更新すると他方に影響します。参照:developer.mozilla.org/en/docs/Web/JavaScript/Reference/…(「ディープクローンの警告」セクション)。それは言う:深いクローニングのために、私たちは他の代替手段を使用する必要があります。これは、割り当てられるプロパティがオブジェクトである場合、Object.assign()がプロパティ参照をコピーするためです。
Meir

47

これを試して:

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

非常に大きなオブジェクトを使用するか、オブジェクトにシリアル化できないプロパティがあるまでは、これは良い解決策です。

型の安全性を維持するために、コピー元のクラスでコピー関数を使用できます。

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

または静的な方法で:

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

5
これは問題ありませんが、シリアライズ/解析時にプロトタイプ情報とjsonでサポートされていないすべてのタイプが失われることに注意してください。
Stanislav E. Govorov 2017年

1
また、これは上記の deepCopy関数に比べて効率が悪いようです。
Mojtaba

「(JSON.parse(JSON.stringify(objectToCopy)));」を使用すると、「エラーを循環構造をJSONに変換する」というエラーが発生します。
セドリックアーノールド

ケースの98%でのみ機能します。undefined少なくとも、値を持つキーが欠落する可能性があります。もしobjectToCopy = { x : undefined};あなたのコードを実行した後の場合Object.keys(objectToCopy).length1、そうObject.keys(copy).lengthです0
アイディン

33

TypeScript / JavaScriptには、浅い複製のための独自の演算子があります。

let shallowClone = { ...original };

15

TypeScript 2.1で導入された「オブジェクトスプレッド」で浅いコピーを取得するのは簡単です

このTypeScript: let copy = { ...original };

この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


2
注:これにより浅いコピーが作成されます
Jimmy Kane

11

シリアル化可能なディープクローンの場合、タイプ情報は次のとおりです。

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

1
これにより、小道具の順序を変更できます。一部の人への警告です。また、日付を適切に処理しません。
Pangamma、

これにより、小道具の順序を変更できます。代わりにnpmjs.com/package/es6-json-stable-stringifyを試してみてくださいJSON.stringify
Polv

@Polv、誰かがオブジェクトのキーの順序に依存している場合、私は彼らよりも大きな問題があると思いますclone。:)
アイディン

このソリューションは、undefined価値のあるキーを見逃す可能性があります。:上記と同様の答えに私のコメントを参照してくださいstackoverflow.com/questions/28150967/typescript-cloning-object/...
Aidin

7

私の見解:

Object.assign(...) プロパティのみをコピーすると、プロトタイプとメソッドが失われます。

Object.create(...) プロパティをコピーしてプロトタイプを作成するだけではありません。

私のために働いたのは、を使用してプロトタイプを作成し、Object.create(...)それを使用してそれにプロパティをコピーすることObject.assign(...)です:

したがって、オブジェクトの場合はfoo、次のようにクローンを作成します。

Object.assign(Object.create(foo), foo)

ここで非常に微妙なことが起こっています。あなたは実際に(新しいオブジェクト)のfoo親をプロトタイプにしていますclonedFoo。これは問題ないように聞こえるかもしれませんが、プロトタイプチェーンで欠落しているプロパティが検索されるためconst a = { x: 8 }; const c = Object.assign(Object.create(a), a); delete c.x; console.log(c.x);、8が出力されることに注意してくださいundefined。(REPLリンク:repl.it/repls/CompetitivePreemptiveKeygen
Aidin

さらに、後でプロパティをに追加するとfoo、自動的にclonedFoo!例えば、foo.y = 9; console.log(clonedFoo.y)プリントアウトされます9代わりにundefined。それはあなたが求めているものではない可能性が非常に高いです!
アイディン

@Aidinでは、深いコピーを確保する方法は?
Muhammad Ali

値によるコピーを再帰的に実行しているこの質問の他の解決策(たとえば、marckassayのstackoverflow.com/a/53025968)は、ターゲットオブジェクトで維持されているソースオブジェクトへの参照がないため、これを保証します。
アイディン

5

次のようなものを持つこともできます:

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

cloneすべてのEntityサブクラスでメソッドをオーバーライドしていることを確認してください。オーバーライドしないと、部分的なクローンが作成されます。

の戻り値の型は、this常にインスタンスの型と一致します。


4

に追加"lodash.clonedeep": "^4.5.0"しますpackage.json。次に、次のように使用します。

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

3

このエラーが発生した場合:

TypeError: this.constructor(...) is not a function

これは正しいスクリプトです:

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

4
正しいcloneObj[attribut] = this.clone();ですか?またはあなたが意味することcloneObj[attribut] = this[attribut].clone();
セルジニョ2017年

2

私自身もこの問題に遭遇し、最終的に、抽象クラスを提供する小さなライブラリcloneable-tsを作成しました。これは、それを拡張するクラスにクローンメソッドを追加します。抽象クラスは、フェントンのみ置き換えることによって、受け入れ答えで説明したディープ・コピー機能を借用copy = {};してcopy = Object.create(originalObj)、元のオブジェクトのクラスを維持するために。クラスの使用例を次に示します。

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

または、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

これがマッシュアップです!そして、これStackBlitzへのリンクです。現在は単純型とオブジェクト型のコピーのみに制限されていますが、簡単に変更できると思います。

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

1
私が見る限り、かなりうまくいきます。ただし、typeof nullもオブジェクトであるため、if (source[P] !== null && typeof source[P] === 'object')代わりにクエリを使用する必要があります。そうでない場合、null値は空のオブジェクトになります。
MortenMoulder

1

TypeScript 3.7がリリースされてから、再帰的な型エイリアスがサポートされ、型セーフなdeepCopy()関数を定義できるようになりました。

// 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

遊び場


0

ホールオブジェクトのコンテンツの単純なクローンの場合は、インスタンスを文字列化して解析するだけです。

let cloneObject = JSON.parse(JSON.stringify(objectToClone))

objectToCloneツリーのデータを変更するのに対し、cloneObjectには変更がありません。それが私の要求でした。

それが役に立てば幸い


1
undefined値のあるキーを見逃す可能性があります。:上記と同様の答えに私のコメントを参照してくださいstackoverflow.com/questions/28150967/typescript-cloning-object/...
Aidin

0

私は結局しました:

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

なぜなら:

var cloneObj = new (<any>this.constructor());

@Fentonからランタイムエラーが発生しました。

Typescriptバージョン:2.4.2


0

古き良きjQueryはどうですか?ここにディープクローンがあります:

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

この質問にはJQueryのタグが付けられておらず、質問でJQueryも言及されていません。また、ディープクローンを作成するためだけにJQueryをプロジェクトに含めることは、大きなオーバーヘッドになります。
LewisM

それは十分に公平ですが、OPはクローンの作成方法ではなく、彼が提供したコードの問題を特定することであり、実際に質問に答えることなくjQueryの方法でクローンを作成しました。あなたに反対票を投じたのは私ではありませんが、それがあなたが反対票を投じられた理由かもしれません。
LewisM

0

ネストされたオブジェクトのタイプを保持する汎用のコピー/クローンサービスを作成するのに一手間かかりました。私が何か間違ったことをしている場合はフィードバックが大好きですが、これまでのところ機能しているようです...

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

typeScriptでは、angularでテストしましたが、問題はありません

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.");
    }

0

別のオブジェクト、配列などを含むことができるオブジェクトをディープクローニングするには、次を使用します。

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
}

使用する:

const obj = {a: [1,2], b: 's', c: () => { return 'h'; }, d: null, e: {a:['x'] }}
const objClone = clone(obj)

0

展開構文で分解代入を使用できます

var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};

1
このコードは質問に答えることがありますが、問題を解決する方法や理由に関する追加のコンテキストを提供すると、回答の長期的な価値が向上します。
ヒョウ

-2

ターゲットオブジェクトが既にあるので、新しく作成する必要がない場合(配列を更新する場合など)、プロパティをコピーする必要があります。
このようにした場合:

Object.keys(source).forEach((key) => {
    copy[key] = source[key]
})

賞賛の期限です。(見出し「バージョン2」を見てください)


関数?配列?日付オブジェクト?タイプの保存?そしてもちろん、オブジェクトはどうですか?上記の関数が上記のタイプのいずれかを検出すると、ディープクローンに失敗します。同じデータへの参照をコピーしました。クローンされたオブジェクトの子プロパティを編集しようとすると、最終的に元のオブジェクトも編集されます。
Pangamma、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.