Ось декілька швидких кадрів у цьому, щоб показати кілька різних способів. Вони аж ніяк не "завершені", і як відмова від відповідальності, я не думаю, що робити це так добре. Також код не надто чистий, оскільки я його досить швидко набрав разом.
Також як зауваження: Звичайно, у десериалізаційних класів повинні бути конструктори за замовчуванням, як це відбувається у всіх інших мовах, де мені відомо про десеріалізацію будь-якого типу. Звичайно, Javascript не поскаржиться, якщо ви викликаєте конструктор, що не працює за замовчуванням, без аргументів, але клас краще тоді бути готовим до цього (плюс, це насправді не був би "typecripty way").
Варіант №1: Інформація про час роботи взагалі відсутня
Проблема такого підходу полягає в тому, що ім’я будь-якого члена повинно відповідати його класу. Це автоматично обмежує вас одним членом одного типу в класі та порушує кілька правил належної практики. Я настійно раджу проти цього, але просто перерахуйте його тут, тому що це був перший "чернетка", коли я написав цю відповідь (саме тому імена "Foo" тощо).
module Environment {
export class Sub {
id: number;
}
export class Foo {
baz: number;
Sub: Sub;
}
}
function deserialize(json, environment, clazz) {
var instance = new clazz();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment, environment[prop]);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
baz: 42,
Sub: {
id: 1337
}
};
var instance = deserialize(json, Environment, Environment.Foo);
console.log(instance);
Варіант №2: Назва Властивість
Щоб позбутися проблеми у варіанті №1, нам потрібно мати якусь інформацію про те, що тип вузла в об’єкті JSON. Проблема полягає в тому, що в Typescript ці речі є конструкціями часу компіляції, і вони нам потрібні під час виконання, але об'єкти часу просто не знають про свої властивості, поки вони не будуть встановлені.
Один із способів зробити це - ознайомити класи з їхніми назвами. Ця власність вам також потрібна в JSON. Насправді вам це потрібно лише в json:
module Environment {
export class Member {
private __name__ = "Member";
id: number;
}
export class ExampleClass {
private __name__ = "ExampleClass";
mainId: number;
firstMember: Member;
secondMember: Member;
}
}
function deserialize(json, environment) {
var instance = new environment[json.__name__]();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], environment);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
__name__: "ExampleClass",
mainId: 42,
firstMember: {
__name__: "Member",
id: 1337
},
secondMember: {
__name__: "Member",
id: -1
}
};
var instance = deserialize(json, Environment);
console.log(instance);
Варіант №3: явно вказується типи членів
Як було сказано вище, інформація про тип членів класу недоступна під час виконання - тобто, якщо ми не зробимо її доступною. Нам потрібно це робити лише для первісних членів, і нам добре:
interface Deserializable {
getTypes(): Object;
}
class Member implements Deserializable {
id: number;
getTypes() {
// since the only member, id, is primitive, we don't need to
// return anything here
return {};
}
}
class ExampleClass implements Deserializable {
mainId: number;
firstMember: Member;
secondMember: Member;
getTypes() {
return {
// this is the duplication so that we have
// run-time type information :/
firstMember: Member,
secondMember: Member
};
}
}
function deserialize(json, clazz) {
var instance = new clazz(),
types = instance.getTypes();
for(var prop in json) {
if(!json.hasOwnProperty(prop)) {
continue;
}
if(typeof json[prop] === 'object') {
instance[prop] = deserialize(json[prop], types[prop]);
} else {
instance[prop] = json[prop];
}
}
return instance;
}
var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};
var instance = deserialize(json, ExampleClass);
console.log(instance);
Варіант №4: багатослівний, але акуратний спосіб
Оновлення 01.03.2016: Як в коментарях ( ідея , реалізація) зазначив @GameAlchemist ) ) , рішення, описане нижче, може бути написано краще, використовуючи декоратори класу / властивості.
Серіалізація - це завжди проблема, і, на мою думку, найкращий спосіб - це спосіб, який просто не найкоротший. З усіх варіантів я б хотів цього, тому що автор класу має повний контроль над станом десеріалізованих об'єктів. Якби мені довелося здогадуватися, я б сказав, що всі інші варіанти рано чи пізно приведуть вас у біду (якщо тільки Javascript не придумає рідний спосіб вирішення цього питання).
Дійсно, наступний приклад не забезпечує справедливості гнучкості. Це дійсно просто копіює структуру класу. Однак, ви повинні мати на увазі, що клас має повний контроль, щоб використовувати будь-який тип JSON, який він хоче контролювати стан усього класу (ви можете обчислити речі тощо).
interface Serializable<T> {
deserialize(input: Object): T;
}
class Member implements Serializable<Member> {
id: number;
deserialize(input) {
this.id = input.id;
return this;
}
}
class ExampleClass implements Serializable<ExampleClass> {
mainId: number;
firstMember: Member;
secondMember: Member;
deserialize(input) {
this.mainId = input.mainId;
this.firstMember = new Member().deserialize(input.firstMember);
this.secondMember = new Member().deserialize(input.secondMember);
return this;
}
}
var json = {
mainId: 42,
firstMember: {
id: 1337
},
secondMember: {
id: -1
}
};
var instance = new ExampleClass().deserialize(json);
console.log(instance);