JSON 使用 getter/setter 字符串化 ES6 类属性

IT技术 javascript ecmascript-6 stringify es6-class
2021-03-16 07:51:48

我有一个 JavaScript ES6 类,它有一个set使用get函数设置和访问的属性它也是一个构造函数参数,因此可以使用所述属性实例化该类。

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 的实例(它发送带有propertynot 的对象_property)。

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

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

6个回答

您可以使用toJSONmethod来自定义您的类序列化为 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
    }
  }
}
不过,我建议调用 getter,而不是 ._property
2021-05-03 07:51:48
谢谢,应该想到这个!
2021-05-03 07:51:48

如果你想避免调用 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;
  }

}
仅供参考,这也适用于构造函数风格的“类”。
2021-04-29 07:51:48
这是一个好点。无论如何,当您的类中有更多属性时,toJSON 方法会变得更大更复杂。每次添加/删除属性时都需要更新它。我仍然认为最好避免它。
2021-05-04 07:51:48
你不叫toJSON它由 自动调用JSON.stringify请参阅JSON.stringify步骤 12 和SerializeJSONProperty步骤 3。
2021-05-12 07:51:48

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

此外,为了避免每次向类添加属性时都重新更新方法,您可以使用更通用的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
};
我已经编辑了答案以更准确地满足所需的结果(和其他常见用例)。
2021-04-22 07:51:48
你说的对。我没有对问题的细节给予足够的关注 :( 无论如何,它很容易修复。只需省略 Object.assign 调用并将其替换为一个空对象。这样您就可以获得没有字段的所有属性。
2021-04-23 07:51:48
这似乎同时产生_propertyproperty
2021-05-01 07:51:48
我实际上在 TypeScript 中使用它,它可以以任何一种方式工作,但你是对的,我已经更新了使用的答案Object.getOwnPropertyNames,谢谢!至于第二部分,正如您从现在可运行的代码段中看到的那样,它似乎有效。
2021-05-17 07:51:48
我必须进行 2 处更改才能使此代码工作:替换const key of Object.keys(proto)(仅返回可枚举属性)与const key of Object.getOwnPropertyNames(proto)(返回所有属性)和jsonObj[key] = desc.get();(返回“未定义”而不是属性的值)与jsonObj[key] = this[key];
2021-05-20 07:51:48

我对 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;
    }
这个效果很好,可以很容易地放入基类。伟大的!
2021-05-05 07:51:48

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

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