web-dev-qa-db-fra.com

Object.defineProperty sur un prototype empêche JSON.stringify de le sérialiser

J'utilise TypeScript pour définir certaines classes et lorsque je crée une propriété, cela génère l'équivalent de Class1 dans le fichier plunkr suivant:

http://plnkr.co/edit/NXUo7zjJZaUuyv54TD9i?p=preview

var Class1 = function () {
  this._name = "test1";
}

Object.defineProperty(Class1.prototype, "Name", {
  get: function() { return this._name; },
  set: function(value) { this._name = value; },
  enumerable: true
});

JSON.stringify(new Class1()); // Will be "{"_name":"test1"}"

Lors de la sérialisation, la propriété que je viens de définir n'est pas sortie.

instance2 et instance3 se comportent comme prévu en sérialisant la propriété définie. (voir la sortie plunkr).

Ma vraie question est: Est-ce normal?

Si oui, comment puis-je le contourner de la manière la plus efficace?

15
Christian Droulers

Vous pouvez définir une méthode toJSON() sur votre prototype pour personnaliser le mode de sérialisation des instances.

Class1.prototype.toJSON = function () {
    return {
        Name: this.Name
    };
};
JSON.stringify(new Class1()); // Will be '{"Name":"test1"}'
17
zzzzBov

Si vous aimez le faire avancer, essayez la proposition de décorateurs pour TypeScript:

Selon cette proposition ( https://github.com/wycats/javascript-decorators ):

Un décorateur est:

  • une expression
  • qui évalue à une fonction
  • qui prend la description de la cible, du nom et de la propriété comme arguments
  • et retourne éventuellement un descripteur de propriété à installer sur l'objet cible

Cela va comme ceci (vue de haut niveau):

Code client:

@serializable()
class Person {

    constructor(name: string) {
      this._name = name;
    }

    private _name: string;

    @serialize()
    get name() {
      return this._name;
    }

    @serialize('Language')
    get lang() {
      return 'JavaScript';
    }
}

Infrastructure:

const serialized = new WeakMap();

export function serializable(name?: string) {
    return function (target, propertyKey, descriptor) {
        target.prototype.toJSON = function () {
            const map = serialized.get(target.prototype);
            const props = Object.keys(map);
            return props.reduce((previous, key) => {
                previous[map[key]] = this[key];
                return previous;
            }, {});
        }

    }
}

export function serialize(name?: string) {
    return function (target, propertyKey, descriptor) {
        let map = serialized.get(target);
        if (!map) {
            map = {};
            serialized.set(target, map);
        }

        map[propertyKey] = name || propertyKey;
    }
}

UPDATE: J'ai extrait cet exemple de décorateur dans un référentiel situé à https://github.com/awerlang/es-decorators

15
André Werlang

Oui, c'est par conception.

Comme défini dans ECMA, seuls propres propriétés énumérables sont sérialisés (stringify() est défini en termes de Object.keys(), entre autres).

Les accesseurs de propriétés sont définis sur des prototypes, à la fois dans TypeScript et ES6.

Et pour répondre à votre dernière question, c’est le moyen le plus efficace d’effectuer cette opération.

À côté de cela, seulement a) définissant un toJSON () pour chaque objet faisant partie de la sérialisation, ou b) transmettant une fonction/un tableau de remplacement en tant que deuxième argument à JSON.stringify ().

Liste blanche des propriétés du prototype:

JSON.stringify(instance, Object.keys(instance.constructor.prototype))
7
André Werlang

Mettre quelque chose ici, espérons-le, peut aider les autres. Ce que j'ai à faire pour corriger JSON.stringify ne sérialise pas les propriétés définies sur le prototype

var newObject = $.extend(false, {}, orginalObj);

alors, je remarque que newObject a des propriétés d’occurrence plutôt que des propriétés de prototype. J'utilise TypeScript et get accessor.

1
beibeizhu

Comme vous l'avez découvert, les propriétés définies sur le prototype ne seront pas sérialisées.

Je sais que ce n'est pas idéal, mais une autre option est de faire ceci:

class Class1 {
    private _name = "test1";

    Name: string; // do this to make the compiler happy

    constructor() {
        Object.defineProperty(this, "Name", {
            get: function() { return this._name; },
            set: function(value) { this._name = value; },
            enumerable: true
        });
    }
}

Définir la propriété sur l'instance sérialisera la propriété.

1
David Sherret