【问题标题】:Object.defineProperty on a prototype prevents JSON.stringify from serializing it原型上的 Object.defineProperty 阻止 JSON.stringify 对其进行序列化
【发布时间】:2015-06-24 15:49:44
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript json typescript


    【解决方案1】:

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

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

    【讨论】:

    • 我将此标记为答案,因为我最终自己实现了 toJSON。我已经有一个私有成员到公共属性的映射,因为我正在使用它将一些 JSON 从我的 API 映射到我的 TypeScript 类。在 toJson 方法中,我只是使用该映射来创建一个新对象,该对象的所有属性都具有倒置的名称。我会尽快写博客。
    【解决方案2】:

    如果您想推动它,请尝试装饰器对 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的存储库中

    【讨论】:

    • 非常有趣!但对于这个项目来说,这在短期内并不是一个可行的解决方案。谢谢!
    • 是的,你是对的,但我觉得它很有前途,希望很快在生产中使用它
    • @Jacks 也许您使用类作为键而不是类的原型来检索?我用一个有效的实现更新了我的答案,所以看看吧。
    • @AndréWerlang 非常感谢您的更新。是的,我确实是第一次尝试通过传递类而不是其原型来检索值。但是为了验证,我试图打印整个 WeakMap,这是不可能的,因为键不可枚举(请参阅this)。感谢您在多年后澄清这篇文章:)
    • @Apidcloud ,类似这样(注意我删除了块,这段代码不会运行,但我添加了箭头以显示ClassDecoratorPropertyDecorator gist.github.com/theredpea/c4e6c7923467db57b6afd867a0e1ecc1 的使用
    【解决方案3】:

    是的,这是设计使然。

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

    在 TypeScript 和 ES6 中都在原型上定义了属性访问器。

    回答您的最后一个问题,这是执行此操作的最有效方式。

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

    原型的白名单属性:

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

    【讨论】:

    • 我采用了 toJSON 方法。我会尽快写博客。
    【解决方案4】:

    在这里放一些东西希望可以帮助其他人。 我所做的修复 JSON.stringify 不序列化原型上定义的属性

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

    然后我注意到 newObject 具有实例属性而不是原型属性。 我正在使用打字稿并获取访问器。

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

      • 我考虑过这个,但我想要一个使用 TypeScript 本身的解决方案。
      • @ChristianDroulers 是的,我也不会使用这个解决方案:)...添加 toJSON 方法是一个更好的选择。
      猜你喜欢
      • 2012-09-04
      • 2020-09-12
      • 2017-03-20
      • 2021-09-12
      • 2015-07-14
      • 1970-01-01
      • 1970-01-01
      • 2019-03-24
      相关资源
      最近更新 更多