Object.defineProperty на прототипе предотвращает сериализацию JSON.stringify

Я использую TypeScript для определения некоторых классов, и когда я создаю свойство, он генерирует эквивалент Class1 в следующем plunkr:

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

При сериализации он не выводит свойство, которое я только что определил.

instance2 и instance3 ведут себя так, как я ожидал, путем сериализации определенного свойства. (см. вывод plunkr).

Мой реальный вопрос: это нормально?

Если да, то как мне с ним работать наиболее эффективным способом?

Ответы

Ответ 1

Вы можете определить метод toJSON() на вашем прототипе, чтобы настроить способ генерации экземпляров.

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

Ответ 2

Если вам нравится продвигать его вперед, попробуйте предложение декораторов для TypeScript:

В соответствии с этим предложением (https://github.com/wycats/javascript-decorators):

Декоратор:

  • выражение
  • который вычисляет функцию
  • который принимает дескриптор target, name и property в качестве аргументов
  • и необязательно возвращает дескриптор свойства для установки на целевой объект

Это выглядит так (вид высокого уровня):

Клиентский код:

@serializable()
class Person {

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

    private _name: string;

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

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

Инфраструктура:

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. Я извлек этот декоратор в хранилище в https://github.com/awerlang/es-decorators

Ответ 3

Да, это по дизайну.

Как определено в ECMA, только собственные перечислимые свойства сериализуются (stringify() определяется, в частности, Object.keys()).

Атрибуты доступа к ресурсам определяются на прототипах, как в TypeScript, так и в ES6.

И отвечая на ваш последний вопрос, это самый эффективный способ выполнить эту операцию.

Кроме того, только a) определяет toJSON() для каждой объектной части сериализации или b) передает функцию-заменитель/массив как второй аргумент в JSON.stringify().

Свойства whitelist из прототипа:

JSON.stringify(instance, Object.keys(instance.constructor.prototype))

Ответ 4

Как вы обнаружили, он не будет сериализовать свойства, определенные на прототипе.

Я знаю, что это не идеальный вариант, но еще один вариант:

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

Определение свойства экземпляра будет сериализовать свойство.

Ответ 5

Поместите что-то здесь, надеюсь, может помочь другим. Что я сделал для исправления JSON.stringify не сериализует свойства, определенные на прототипе

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

то я замечаю, что newObject имеет свойства экземпляра вместо свойств прототипа. Я использую typescript и получаю accessor.