web-dev-qa-db-fra.com

Propriété de classe JSON stringify ES6 avec getter / setter

J'ai une classe JavaScript ES6 qui a une propriété définie avec set et accessible avec les fonctions get. C'est aussi un paramètre constructeur pour que la classe puisse être instanciée avec ladite propriété.

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

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

  get property() {
    return this._property
  }
}

J'utilise _property pour échapper au piège JS de l'utilisation de get/set qui entraîne une boucle infinie si je mets directement à property.

Maintenant, je dois stringifier une instance de MyClass pour l'envoyer avec une requête HTTP. Le JSON stringifié est un objet comme:

{
   //...
   _property:
}

J'ai besoin de la chaîne JSON résultante pour conserver property afin que le service auquel je l'envoie puisse l'analyser correctement. J'ai également besoin de property pour rester dans le constructeur car j'ai besoin de construire des instances de MyClass à partir de JSON envoyées par le service (qui envoie des objets avec property pas _property).

Comment puis-je contourner cela? Dois-je simplement intercepter l'instance MyClass avant de l'envoyer à la demande HTTP et muter _property à property en utilisant l'expression régulière? Cela semble moche, mais je pourrai conserver mon code actuel.

Alternativement, je peux intercepter le JSON envoyé au client à partir du service et instancier MyClass avec un nom de propriété totalement différent. Cependant, cela signifie une représentation différente de la classe de chaque côté du service.

25
Thomas Chia

Vous pouvez utiliser méthode toJSON pour personnaliser la façon dont votre classe se sérialise en 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
    }
  }
}
33
Amadan

Si vous voulez éviter d'appeler toJson, il existe une autre solution utilisant énumérable et inscriptible:

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;
  }

}
14
Richard Časár

Comme mentionné par @Amadan, vous pouvez écrire votre propre méthode toJSON .

De plus, afin d'éviter de mettre à jour votre méthode chaque fois que vous ajoutez une propriété à votre classe, vous pouvez utiliser une implémentation toJSON plus générique.

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"}

Si vous souhaitez émettre toutes les propriétés et tous les champs , vous pouvez remplacer const jsonObj = {}; avec

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

Alternativement, si vous souhaitez émettre toutes les propriétés et certains champs spécifiques , vous pouvez le remplacer par

const jsonObj = {
    myField: myOtherField
};
9
Alon Bar

J'ai fait quelques ajustements au script d'Alon Bar. Voici une version du script qui fonctionne parfaitement pour moi.

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;
    }
7
bits

Utilisez champs privés pour un usage interne.

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