JSON 将 ES6 class 属性 字符串化为 getter/setter

JSON stringify ES6 class property with getter/setter

我有一个 JavaScript ES6 class,它有一个 属性 设置 set 并使用 get 函数访问。它也是一个构造函数参数,因此 class 可以用 属性.

实例化
class MyClass {
  constructor(property) {
    this.property = property
  }

  set property(prop) {
  // Some validation etc.
  this._property = prop
  }

  get property() {
    return this._property
  }
}

我使用 _property 来避免使用 get/set 的 JS 问题,如果我直接设置为 property.

会导致无限循环

现在我需要将 MyClass 的一个实例字符串化,以便通过 HTTP 请求发送它。字符串化的 JSON 是一个像这样的对象:

{
   //...
   _property:
}

我需要生成的 JSON 字符串来保留 property 以便我发送它的服务可以正确解析它。我还需要 property 保留在构造函数中,因为我需要从服务发送的 JSON 构造 MyClass 的实例(它正在发送带有 property 而不是 _property 的对象)。

我该如何解决这个问题?我是否应该在将 MyClass 实例发送到 HTTP 请求之前拦截它并使用正则表达式将 _property 变异为 property?这看起来很难看,但我将能够保留我当前的代码。

或者,我可以拦截从服务发送到客户端的 JSON 并使用完全不同的 属性 名称实例化 MyClass。然而,这意味着 class 服务任一侧的不同表示。

您可以使用 toJSON method 自定义 class 序列化为 JSON 的方式:

class MyClass {
  constructor(property) {
    this.property = property
  }

  set property(prop) {
  // Some validation etc.
  this._property = prop
  }

  get property() {
    return this._property
  }

  toJSON() {
    return {
      property: this.property
    }
  }
}

如果你想避免调用toJson,还有一个使用enumerable和writable的解决方案:

class MyClass {

  constructor(property) {

    Object.defineProperties(this, {
        _property: {writable: true, enumerable: false},
        property: {
            get: function () { return this._property; },
            set: function (property) { this._property = property; },
            enumerable: true
        }
    });

    this.property = property;
  }

}

如@Amadan 所述,您可以编写自己的 toJSON 方法。

此外,为了避免在每次向 class 添加 属性 时使用 re-updating 您的方法,您可以使用更通用的 toJSON 实现。

class MyClass {

  get prop1() {
    return 'hello';
  }
  
  get prop2() {
    return 'world';
  }

  toJSON() {

    // start with an empty object (see other alternatives below) 
    const jsonObj = {};

    // add all properties
    const proto = Object.getPrototypeOf(this);
    for (const key of Object.getOwnPropertyNames(proto)) {      
      const desc = Object.getOwnPropertyDescriptor(proto, key);
      const hasGetter = desc && typeof desc.get === 'function';
      if (hasGetter) {
        jsonObj[key] = desc.get();
      }
    }

    return jsonObj;
  }
}

const instance = new MyClass();
const json = JSON.stringify(instance);
console.log(json); // outputs: {"prop1":"hello","prop2":"world"}

如果您想发出所有属性和所有字段,您可以将const jsonObj = {};替换为

const jsonObj = Object.assign({}, this);

或者,如果您想发出所有属性和一些特定字段,您可以将其替换为

const jsonObj = {
    myField: myOtherField
};

我对Alon Bar的剧本做了一些调整。下面是一个非常适合我的脚本版本。

toJSON() {
        const jsonObj = Object.assign({}, this);
        const proto = Object.getPrototypeOf(this);
        for (const key of Object.getOwnPropertyNames(proto)) {
            const desc = Object.getOwnPropertyDescriptor(proto, key);
            const hasGetter = desc && typeof desc.get === 'function';
            if (hasGetter) {
                jsonObj[key] = this[key];
            }
        }
        return jsonObj;
    }

使用私有字段供内部使用。

class PrivateClassFieldTest {
    #property;
    constructor(value) {
        this.property = value;
    }
    get property() {
        return this.#property;
    }
    set property(value) {
        this.#property = value;
    }
}

class Test {
 constructor(value) {
  this.property = value;
 }
 get property() {
  return this._property;
 }
 set property(value) {
  this._property = value;
 }
}

class PublicClassFieldTest {
 _property;
 constructor(value) {
  this.property = value;
 }
 get property() {
  return this.property;
 }
 set property(value) {
  this._property = value;
 }
}

class PrivateClassFieldTest {
 #property;
 constructor(value) {
  this.property = value;
 }
 get property() {
  return this.#property;
 }
 set property(value) {
  this.#property = value;
 }
}

console.log(JSON.stringify(new Test("test")));
console.log(JSON.stringify(new PublicClassFieldTest("test")));
console.log(JSON.stringify(new PrivateClassFieldTest("test")));

我做了一个名为 esserializer 的 npm 模块来解决这样的问题:将 JavaScript class 的实例字符串化,以便它可以通过 HTTP 请求发送:

// Client side
const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfMyClass);
// Send HTTP request, with serializedText as data

在服务端,再次使用esserializer将数据反序列化为anInstanceOfMyClass的完美副本,并保留所有getter/setter字段(例如property):

// Node.js service side
const deserializedObj = ESSerializer.deserialize(serializedText, [MyClass]);
// deserializedObj is a perfect copy of anInstanceOfMyClass