web-dev-qa-db-ja.com

プロトタイプのObject.definePropertyは、JSON.stringifyによる直列化を防止します

TypeScriptを使用していくつかのクラスを定義しています。プロパティを作成すると、次のplunkrにClass1と同等のものが生成されます。

http://plnkr.co/edit/NXUo7zjJZaUuyv54TD9i?p=preview

var Class1 = function () {
  this._name = "test1";
}

Object.defineProperty(Class1.prototype, "Name", {
  get: function() { return this._name; },
  set: function(value) { this._name = value; },
  enumerable: true
});

JSON.stringify(new Class1()); // Will be "{"_name":"test1"}"

シリアル化するとき、定義したプロパティは出力されません。

instance2instance3は、定義されたプロパティをシリアル化することで期待どおりに動作します。 (plunkrの出力を参照してください)。

私の実際の質問は:これは正常ですか?

もしそうなら、どうすれば最も効率的な方法でそれを回避できますか?

19

プロトタイプで toJSON() メソッドを定義して、インスタンスのシリアル化方法をカスタマイズできます。

Class1.prototype.toJSON = function () {
    return {
        Name: this.Name
    };
};
JSON.stringify(new Class1()); // Will be '{"Name":"test1"}'
19
zzzzBov

プッシュしたい場合は、TypeScriptのデコレータ提案を試してください。

この提案によると( https://github.com/wycats/javascript-decorators ):

デコレータは:

  • 表現
  • 関数に評価される
  • ターゲット、名前、プロパティ記述子を引数として受け取ります
  • オプションで、ターゲットオブジェクトにインストールするプロパティ記述子を返します

それはこのようになります(高レベルのビュー):

クライアントコード:

@serializable()
class Person {

    constructor(name: string) {
      this._name = name;
    }

    private _name: string;

    @serialize()
    get name() {
      return this._name;
    }

    @serialize('Language')
    get lang() {
      return 'JavaScript';
    }
}

インフラ:

const serialized = new WeakMap();

export function serializable(name?: string) {
    return function (target, propertyKey, descriptor) {
        target.prototype.toJSON = function () {
            const map = serialized.get(target.prototype);
            const props = Object.keys(map);
            return props.reduce((previous, key) => {
                previous[map[key]] = this[key];
                return previous;
            }, {});
        }

    }
}

export function serialize(name?: string) {
    return function (target, propertyKey, descriptor) {
        let map = serialized.get(target);
        if (!map) {
            map = {};
            serialized.set(target, map);
        }

        map[propertyKey] = name || propertyKey;
    }
}

[〜#〜] update [〜#〜]:このサンプルデコレータを https://github.comのリポジトリに抽出しました/ awerlang/es-decorators

17
André Werlang

はい、仕様です。

ECMAで定義されているように、シリアル化されるのは独自の列挙可能なプロパティだけです(stringify()Object.keys()の観点から定義されています)。

プロパティアクセサーは、TypeScriptとES6の両方のプロトタイプで定義されます。

そして、あなたの最後の質問に答えてください、それはこの操作を実行する最も効率的な方法です。

それ以外には、a)シリアル化の各オブジェクト部分にtoJSON()を定義するか、またはb)置換関数/配列をJSON.stringify()の2番目の引数として渡すだけです。

プロトタイプのホワイトリストプロパティ:

JSON.stringify(instance, Object.keys(instance.constructor.prototype))
8
André Werlang

ここに何かを置くと、他の人を助けることができます。 JSON.stringifyがプロトタイプで定義されたプロパティをシリアル化しないのを修正するために私がしていること

var newObject = $.extend(false, {}, orginalObj);

次に、newObjectにプロトタイププロパティの代わりにインスタンスプロパティがあることに気付きました。 TypeScriptを使用してアクセサーを取得しています。

2
BeiBei ZHU

あなたが発見したように、それはプロトタイプで定義されたプロパティをシリアル化しません。

私はそれが理想的でないことを知っていますが、別のオプションはこれを行うことです:

class Class1 {
    private _name = "test1";

    Name: string; // do this to make the compiler happy

    constructor() {
        Object.defineProperty(this, "Name", {
            get: function() { return this._name; },
            set: function(value) { this._name = value; },
            enumerable: true
        });
    }
}

インスタンスのプロパティを定義すると、プロパティがシリアル化されます。

1
David Sherret