如何使用装饰器使对象的 JSON.stringify 输出吸气剂?

How to make JSON.stringify output getters of an object by using decorators?

我需要 JSON.stringify 方法来输出 getters。我知道它没有这样做,因为 getters 不是“可枚举的”。

我找到了一个解决方案,我正在使用 Object.defineProperty 通过可枚举的 属性 来增大 getter,如 Playground.[=18= 中所示]

我想使用装饰器做同样的事情,但它不像 shown here 那样工作。

请不要建议实施 toJSON 方法,我知道它可能有帮助,但我想要一个仅装饰器的解决方案。

它也与 Angular 相关,因为我需要它来使 formGroup.patchValue 工作 getters。

updated my example 采纳@acdcjunior 的建议 - 希望他们能找到我在这里遗漏的东西。

嗯,我敢肯定你真的不能,据我所知,标准说 JSON.stringify 只会输出对象 own 可枚举属性。我认为最简单的解决方案是使用 JSON.stringifypass in the keys you want to whitelist 的第二个参数,这将使其使用您自己的键列表而不是我假设它们从 [=13= 获得的列表]:

constructor() {
    const obj = new MyClass();
    document.write(JSON.stringify(obj, Object.keys(obj.constructor.prototype)))
}

JSON.stringify(),只序列化对象自身的可枚举属性。使用该装饰器,您可以将吸气剂添加为原型对象的属性。

来自ES6 Spec

24.3.2.3 Runtime Semantics: SerializeJSONObject ( value )

The abstract operation SerializeJSONObject with argument value serializes an object. It has access to the stack, indent, gap, and PropertyList values of the current invocation of the stringify method.

(...)

    1. If PropertyList is not undefined, then
      • Let K be PropertyList.
    1. Else,

相关点是6.

如果你问,PropertyListserialization of arrays 相关,所以它不可能是逃生通道。


使用方法级装饰器,虽然你说你不想要它,但我能想到的唯一方法是让装饰器添加,如果不存在,一个 toJSON 到原型class.

这样的 toJSON 将(仅在首次调用时)将来自 class 原型的 getter(作为可枚举属性)添加到正在调用 toJSON 的实例中。

棘手的部分是,当你装饰了多个 getter 时,要在处理第二个和装饰器时,区分你拥有的 toJSON 是否是由你创建的(然后你可以 "append" 当前装饰器的 属性 )或者如果它最初在 class 中(在这种情况下你会忽略)。棘手,但据我所知,可行。

演示

根据要求,你去吧。 JSFiddle demo here.

class MyClass {
  constructor(public name) {}
  @enumerable(true)
    get location(): string {
    return 'Hello from Location!'
  }
  @enumerable(true)
    get age(): number {
    return 12345;
  }
    get sex(): string {
    return "f";
  }
}
class MyClassWithToJson {
  constructor(public name) {}
  @enumerable(true)
    get nickname(): string {
    return 'I shall still be non enumerable'
  }
  toJSON() { return 'Previously existing toJSON()!' }
}

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string) {
      if (!value) {
        // didnt ask to enumerate, nothing to do because even enumerables at the prototype wont
        // appear in the JSON
        return;
      }
        if (target.toJSON && !target.__propsToMakeEnumerable) {
        return; // previously existing toJSON, nothing to do!
      }
      target.__propsToMakeEnumerable = (target.__propsToMakeEnumerable || []).concat([{propertyKey, value}])

      target.toJSON = function () {
        let self = this; // JSFiddle transpiler apparently is not transpiling arrow functions properly
        if (!this.__propsToMakeEnumerableAlreadyProcessed) { // so we just do this once
          console.log('processing non-enumerable props...'); // remove later, just for testing
          let propsToMakeEnumerable = self.__propsToMakeEnumerable;
          (propsToMakeEnumerable || []).forEach(({propertyKey, value}) => {
              let descriptor = Object.getOwnPropertyDescriptor(self.__proto__, propertyKey);
              descriptor.enumerable = true;
              Object.defineProperty(self, propertyKey, descriptor);
          });
          Object.defineProperty(this, '__propsToMakeEnumerableAlreadyProcessed', {value: true, enumerable: false});
        }
        return this;
      }
  };
}

let obj = new MyClass('Bob');
console.log(JSON.stringify( obj ));
console.log(JSON.stringify( obj )); // this second time it shouldn't print "processing..."
console.log(JSON.stringify( new MyClassWithToJson('MyClassWithToJson') ));

已更新 TypeScript playground link here