Oto kilka szybkich ujęć, które pokazują kilka różnych sposobów. Nie są one w żadnym wypadku „kompletne” i jako wyłączenie odpowiedzialności nie wydaje mi się, aby robić to w ten sposób. Również kod nie jest zbyt czysty, ponieważ napisałem go dość szybko.
Również jako uwaga: Oczywiście klasy deserializowalne muszą mieć domyślne konstruktory, jak ma to miejsce we wszystkich innych językach, w których jestem świadomy wszelkiego rodzaju deserializacji. Oczywiście Javascript nie będzie narzekał, jeśli wywołasz konstruktora, który nie jest domyślny bez argumentów, ale lepiej przygotuj się na klasę (plus, tak naprawdę nie byłby to „typowy sposób”).
Opcja nr 1: Brak informacji o czasie wykonywania
Problem z tym podejściem polega głównie na tym, że nazwa dowolnego członka musi pasować do jego klasy. Co automatycznie ogranicza cię do jednego członka tego samego typu na klasę i łamie kilka zasad dobrej praktyki. Zdecydowanie odradzam to, ale po prostu wymień to tutaj, ponieważ był to pierwszy „szkic”, kiedy napisałem tę odpowiedź (dlatego też imiona to „Foo” itp.).
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);
Opcja nr 2: właściwość name
Aby pozbyć się problemu w opcji nr 1, potrzebujemy informacji o typie węzła w obiekcie JSON. Problem polega na tym, że w Typescript te rzeczy są konstrukcjami czasu kompilacji i potrzebujemy ich w czasie wykonywania - ale obiekty środowiska wykonawczego po prostu nie mają świadomości swoich właściwości, dopóki nie zostaną ustawione.
Jednym ze sposobów jest uświadomienie klasom ich nazw. Tej właściwości potrzebujesz także w JSON. Właściwie potrzebujesz go tylko w 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);
Opcja nr 3: Jawne określenie typów członków
Jak wspomniano powyżej, informacje o typie członków klasy nie są dostępne w środowisku wykonawczym - to znaczy, chyba że je udostępniamy. Musimy to zrobić tylko dla prymitywnych członków i jesteśmy gotowi:
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);
Opcja nr 4: pełna, ale zgrabna droga
Aktualizacja 01.03.2016: Jak zauważyła @GameAlchemist w komentarzach ( pomysł , implementacja ), tak jak w maszynie Typescript 1.7, opisane poniżej rozwiązanie można lepiej napisać przy użyciu dekoratorów klas / właściwości.
Serializacja zawsze stanowi problem i moim zdaniem najlepszym sposobem jest sposób, który po prostu nie jest najkrótszy. Spośród wszystkich opcji wolałbym to, ponieważ autor klasy ma pełną kontrolę nad stanem zdezrializowanych obiektów. Gdybym musiał zgadywać, powiedziałbym, że wszystkie inne opcje, prędzej czy później, wpędzą cię w kłopoty (chyba że Javascript znajdzie sposób na rozwiązanie tego problemu).
Naprawdę, poniższy przykład nie oddaje sprawiedliwości elastyczności. Naprawdę po prostu kopiuje strukturę klasy. Różnica, o której musisz jednak pamiętać, polega na tym, że klasa ma pełną kontrolę nad użyciem dowolnego typu JSON, który chce kontrolować stan całej klasy (możesz obliczyć rzeczy itp.).
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);