Вот несколько быстрых снимков, чтобы показать несколько разных способов. Они ни в коем случае не являются «полными» и, как отказ от ответственности, я не думаю, что это хорошая идея сделать это так. Кроме того, код не слишком чистый, так как я набрал его довольно быстро.
Также как примечание: Конечно, десериализуемые классы должны иметь конструкторы по умолчанию, как это имеет место во всех других языках, где я знаю о десериализации любого рода. Конечно, Javascript не будет жаловаться, если вы вызовете конструктор не по умолчанию без аргументов, но тогда класс должен быть лучше подготовлен к нему (плюс, на самом деле это не будет «типизированный способ»).
Вариант № 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 в комментариях ( идея , реализация ), начиная с Typescript 1.7, решение, описанное ниже, может быть написано лучше с использованием декораторов классов / свойств.
Сериализация - это всегда проблема, и, на мой взгляд, лучший способ - это путь, который не самый короткий. Из всех вариантов это то, что я бы предпочел, потому что автор класса имеет полный контроль над состоянием десериализованных объектов. Если бы мне пришлось угадывать, я бы сказал, что все другие варианты рано или поздно доставят вам неприятности (если только в 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);