Dies sind einige kurze Aufnahmen, um einige verschiedene Möglichkeiten aufzuzeigen. Sie sind keineswegs "vollständig" und als Haftungsausschluss halte ich es nicht für eine gute Idee, dies so zu tun. Außerdem ist der Code nicht zu sauber, da ich ihn nur ziemlich schnell zusammen geschrieben habe.
Auch als Hinweis: Natürlich müssen deserialisierbare Klassen Standardkonstruktoren haben, wie dies in allen anderen Sprachen der Fall ist, in denen mir Deserialisierung jeglicher Art bekannt ist. Natürlich wird sich Javascript nicht beschweren, wenn Sie einen nicht standardmäßigen Konstruktor ohne Argumente aufrufen, aber die Klasse sollte dann besser darauf vorbereitet sein (außerdem wäre es nicht wirklich der "Typenskript-Weg").
Option 1: Überhaupt keine Laufzeitinformationen
Das Problem bei diesem Ansatz besteht hauptsächlich darin, dass der Name eines Mitglieds mit seiner Klasse übereinstimmen muss. Dies beschränkt Sie automatisch auf ein Mitglied desselben Typs pro Klasse und verstößt gegen mehrere Regeln für bewährte Verfahren. Ich rate dringend davon ab, aber liste es einfach hier auf, weil es der erste "Entwurf" war, als ich diese Antwort schrieb (weshalb die Namen auch "Foo" usw. sind).
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);
Option 2: Der Name Eigenschaft
Um das Problem in Option 1 zu beseitigen, benötigen wir Informationen darüber, welcher Typ ein Knoten im JSON-Objekt ist. Das Problem ist, dass diese Dinge in Typescript Konstrukte zur Kompilierungszeit sind und wir sie zur Laufzeit benötigen - aber Laufzeitobjekte kennen ihre Eigenschaften erst, wenn sie festgelegt sind.
Eine Möglichkeit besteht darin, die Klassen auf ihre Namen aufmerksam zu machen. Sie benötigen diese Eigenschaft jedoch auch im JSON. Eigentlich brauchst du es nur im 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);
Option 3: Explizite Angabe der Elementtypen
Wie oben erwähnt, sind die Typinformationen von Klassenmitgliedern zur Laufzeit nicht verfügbar - es sei denn, wir stellen sie zur Verfügung. Wir müssen dies nur für nicht-primitive Mitglieder tun und es kann losgehen:
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);
Option 4: Der ausführliche, aber ordentliche Weg
Update 01/03/2016: Wie @GameAlchemist in den Kommentaren betonte ( Idee , Implementierung ) ab Typescript 1.7 , kann die unten beschriebene Lösung mithilfe von Klassen- / Eigenschaftsdekoratoren besser geschrieben werden.
Serialisierung ist immer ein Problem und meiner Meinung nach ist der beste Weg ein Weg, der einfach nicht der kürzeste ist. Von allen Optionen würde ich dies bevorzugen, da der Autor der Klasse die volle Kontrolle über den Status deserialisierter Objekte hat. Wenn ich raten müsste, würde ich sagen, dass alle anderen Optionen früher oder später Sie in Schwierigkeiten bringen werden (es sei denn, Javascript bietet eine native Methode, um damit umzugehen).
Das folgende Beispiel wird der Flexibilität nicht gerecht. Es kopiert wirklich nur die Struktur der Klasse. Der Unterschied, den Sie hier beachten müssen, besteht darin, dass die Klasse die volle Kontrolle über die Verwendung jeder Art von JSON hat, die den Status der gesamten Klasse steuern soll (Sie können Dinge berechnen usw.).
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);