JSON строит свойство класса ES6 с помощью getter/setter

У меня есть класс 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 not _property).

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

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

Ответ 1

Вы можете использовать toJSON, чтобы настроить способ сериализации класса в 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
    }
  }
}

Ответ 2

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

}

Ответ 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"}

Ответ 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

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

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