JSON Stringify свойство класса ES6 с помощью геттера / сеттера

У меня есть класс 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, чтобы избежать ошибки JS при использовании get / set, которая приводит к бесконечному циклу, если я устанавливаю непосредственно на property.

Теперь мне нужно преобразовать экземпляр MyClass в строку, чтобы отправить его с помощью HTTP-запроса. Строковый JSON - это такой объект, как:

{
   //...
   _property:
}

Мне нужна результирующая строка JSON, чтобы сохранить property, чтобы служба, которой я ее отправляю, могла правильно ее проанализировать. Мне также нужно, чтобы property оставался в конструкторе, потому что мне нужно создавать экземпляры MyClass из JSON, отправленного службой (которая отправляет объекты с property, а не _property).

Как мне обойти это? Должен ли я просто перехватить экземпляр MyClass перед его отправкой в ​​HTTP-запрос и преобразовать _property в property с помощью регулярного выражения? Это кажется некрасивым, но я смогу сохранить свой текущий код.

В качестве альтернативы я могу перехватить JSON, отправляемый клиенту из службы, и создать экземпляр MyClass с совершенно другим именем свойства. Однако это означает различное представление класса по обе стороны службы.


person Thomas Chia    schedule 08.02.2017    source источник
comment
MyClass.prototype.toJson может вам помочь   -  person Farzher    schedule 08.02.2017
comment
@StephenBugsKamenar: toJSON - обратите внимание на заглавные буквы   -  person Amadan    schedule 08.02.2017
comment
По теме: Преобразование класса ES6 с символами в JSON   -  person Bergi    schedule 08.02.2017


Ответы (6)



Если вы хотите избежать вызова 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;
  }

}
person Richard Časár    schedule 09.03.2017
comment
Вы не звоните toJSON. Он автоматически вызывается JSON.stringify. См. JSON.stringify шаг 12 и _ 4_ шаг 3. - person Amadan; 14.03.2017
comment
Это хороший момент. В любом случае, когда у вас больше свойств в вашем классе, метод toJSON становится больше и сложнее. Вам необходимо обновлять его каждый раз, когда вы добавляете / удаляете свойство. Я все же считаю, что этого лучше избегать. - person Richard Časár; 18.03.2017
comment
К вашему сведению, это также работает с классами стиля функций конструктора. - person Micah Epps; 16.02.2018

Внес некоторые коррективы в сценарий Алона Бара. Ниже представлена ​​версия скрипта, которая мне подходит.

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;
    }
person bits    schedule 10.06.2018
comment
Этот отлично работает и может быть легко помещен в базовый класс. Большой! - person Chris Rice; 29.04.2020

Как упомянуто @Amadan, вы можете написать свой собственный _ 1_ метод.

Более того, чтобы избежать повторного обновления вашего метода каждый раз, когда вы добавляете свойство в свой класс, вы можете использовать более общую реализацию 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
};
person Alon Bar    schedule 24.02.2018
comment
Кажется, что и _property, и property - person Bergi; 24.02.2018
comment
Ты прав. Я не уделил достаточно внимания деталям вопроса :( Тем не менее, это легко исправить. Просто опустите вызов Object.assign и замените его пустым объектом. Таким образом вы получите все свойства без полей. - person Alon Bar; 25.02.2018
comment
Я отредактировал ответ, чтобы он более точно соответствовал требуемому результату (и другим распространенным вариантам использования). - person Alon Bar; 25.02.2018
comment
Мне пришлось внести 2 изменения, чтобы этот код заработал: заменить const key of Object.keys(proto) (возвращает только перечислимые свойства) на const key of Object.getOwnPropertyNames(proto) (возвращает все свойства) и jsonObj[key] = desc.get(); (возвращает undefined вместо значения свойства) на jsonObj[key] = this[key]; - person bits; 10.06.2018
comment
На самом деле я использую его в TypeScript, и он работает в любом случае, но вы правы, и я обновил ответ, чтобы использовать Object.getOwnPropertyNames, спасибо! Что касается второй части, как вы можете видеть из уже готового к выполнению фрагмента, похоже, что она работает. - person Alon Bar; 10.06.2018

Используйте закрытые поля для внутреннего использования.

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

person Константин Ван    schedule 24.07.2019

Я создал модуль npm с именем esserializer для решения такой проблемы: преобразовать экземпляр класса JavaScript в строку , чтобы его можно было отправить с HTTP-запросом:

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

На стороне службы снова используйте эссериализатор, чтобы десериализовать данные в идеальную копию anInstanceOfMyClass, с сохранением всех полей геттера / сеттера (например, property):

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