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.