原型上的Object.defineProperty阻止JSON.stringify序列化它

时间:2015-04-17 16:56:00

标签: javascript json typescript

我正在使用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输出)。

我的实际问题是:这是正常的吗?

如果是这样,我该如何以最有效的方式解决这个问题?

4 个答案:

答案 0 :(得分:19)

您可以在原型上定义toJSON()方法,以自定义实例的序列化方式。

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

答案 1 :(得分:16)

如果您想推进它,请尝试针对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;
    }
}

更新:我将此样本装饰器提取到https://github.com/awerlang/es-decorators

的存储库中

答案 2 :(得分:7)

是的,这是设计的。

根据ECMA的定义,只有拥有的可枚举属性被序列化(stringify()是根据Object.keys()定义的。

属性访问器在原型上定义,包括TypeScript和ES6。

回答你的上一个问题,这是执行此操作的最有效方法。

除此之外,只有a)为序列化的每个对象部分定义toJSON(),或b)将替换器函数/数组作为第二个参数传递给JSON.stringify()。

原型中的白名单属性:

JSON.stringify(instance, Object.keys(instance.constructor.prototype))

答案 3 :(得分:1)

正如您所发现的那样,它不会序列化在原型上定义的属性。

我知道这不太理想,但另一种选择是这样做:

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

在实例上定义属性将序列化属性。