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

Posted

技术标签:

【中文标题】JSON 使用 getter/setter 对 ES6 类属性进行字符串化【英文标题】:JSON stringify ES6 class property with getter/setter 【发布时间】:2017-06-25 17:43:37 【问题描述】:

我有一个 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 的实例(它使用 property 而不是 _property 发送对象)。

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

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

【问题讨论】:

MyClass.prototype.toJson 可能会帮助你 @StephenBugsKamenar: toJSON - 注意大写 相关:Convert ES6 Class with Symbols to JSON 【参考方案1】:

我制作了一个名为 esserializer 的 npm 模块来解决这样的问题:字符串化 JavaScript 类的实例,以便它可以与 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

【讨论】:

【参考方案2】:

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

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

【讨论】:

【参考方案3】:

正如@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
;

【讨论】:

这似乎同时产生了_propertyproperty 你是对的。我没有对问题的细节给予足够的关注:(尽管它很容易修复。只需省略 Object.assign 调用并将其替换为空对象。这样您就可以获得没有字段的所有属性。 我已经编辑了答案以更准确地适应所需的结果(和其他常见用例)。 我必须进行 2 处更改才能使此代码正常工作:将 const key of Object.keys(proto)(仅返回可枚举属性)替换为 const key of Object.getOwnPropertyNames(proto)(返回所有属性)和 jsonObj[key] = desc.get();(返回“未定义”而不是属性值)与jsonObj[key] = this[key]; 我实际上在 TypeScript 中使用它,它可以工作,但你是对的,我已经更新了使用 Object.getOwnPropertyNames 的答案,谢谢!至于第二部分,从现在可运行的 sn-p 中可以看出,它似乎可以工作。【参考方案4】:

我对 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;
    

【讨论】:

这个很好用,可以很容易地放入基类中。太好了!【参考方案5】:

如果要避免调用toJson,还有另一种使用可枚举和可写的解决方案:

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;
  


【讨论】:

你不要打电话给toJSON。它由JSON.stringify 自动调用。请参阅JSON.stringify 第 12 步和SerializeJSONProperty 第 3 步。 这是一个很好的观点。无论如何,当你的类中有更多属性时,toJSON 方法会变得更大更复杂。每次添加/删除属性时都需要更新它。我仍然认为最好避免它。 仅供参考,这也适用于构造函数样式“类”。【参考方案6】:

您可以使用toJSON method 自定义您的类序列化为 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 谢谢,早该想到的!

以上是关于JSON 使用 getter/setter 对 ES6 类属性进行字符串化的主要内容,如果未能解决你的问题,请参考以下文章

龙目岛不生成getter和setter?

如何一次生成构造函数、getter、setter、equals&hashcode 和 toString?

Flutter 中的 Getter/Setter 使用 Android Studio

Dart中类的getter和setter

Getter Setter:使用还是不使用?

为啥 GSON 使用字段而不是 getter/setter?