Voici quelques plans rapides pour montrer quelques façons différentes. Ils ne sont en aucun cas "complets" et en tant que déni de responsabilité, je ne pense pas que ce soit une bonne idée de le faire comme ça. De plus, le code n'est pas trop propre car je viens de le taper assez rapidement.
Également comme note: Bien sûr, les classes désérialisables doivent avoir des constructeurs par défaut comme c'est le cas dans tous les autres langages où je suis au courant de la désérialisation de toute sorte. Bien sûr, Javascript ne se plaindra pas si vous appelez un constructeur non par défaut sans arguments, mais la classe devrait alors y être préparée (en plus, ce ne serait pas vraiment la "méthode de la typographie").
Option n ° 1: aucune information d'exécution
Le problème avec cette approche est principalement que le nom d'un membre doit correspondre à sa classe. Ce qui vous limite automatiquement à un membre du même type par classe et enfreint plusieurs règles de bonne pratique. Je déconseille fortement cela, mais inscrivez-le simplement ici parce que c'était le premier "brouillon" quand j'ai écrit cette réponse (c'est aussi pourquoi les noms sont "Foo" etc.).
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: la propriété name
Pour se débarrasser du problème dans l'option # 1, nous devons avoir une sorte d'informations sur le type d'un nœud dans l'objet JSON. Le problème est que dans Typescript, ces choses sont des constructions au moment de la compilation et nous en avons besoin lors de l'exécution - mais les objets d'exécution n'ont tout simplement pas conscience de leurs propriétés jusqu'à ce qu'ils soient définis.
Une façon de le faire consiste à informer les classes de leurs noms. Vous avez également besoin de cette propriété dans le JSON. En fait, vous n'en avez besoin que dans le 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 n ° 3: indiquer explicitement les types de membres
Comme indiqué ci-dessus, les informations de type des membres de la classe ne sont pas disponibles au moment de l'exécution, c'est-à-dire à moins que nous ne les rendions disponibles. Nous n'avons besoin de faire cela que pour les membres non primitifs et nous sommes prêts à partir:
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: La manière verbeuse, mais soignée
Mise à jour du 01/03/2016: Comme @GameAlchemist l'a souligné dans les commentaires ( idée , implémentation ), à partir de Typescript 1.7, la solution décrite ci-dessous peut être mieux écrite à l'aide de décorateurs de classe / propriété.
La sérialisation est toujours un problème et à mon avis, le meilleur moyen est un moyen qui n'est tout simplement pas le plus court. De toutes les options, c'est ce que je préférerais parce que l'auteur de la classe a un contrôle total sur l'état des objets désérialisés. Si je devais deviner, je dirais que toutes les autres options, tôt ou tard, vous causeront des ennuis (à moins que Javascript ne propose une méthode native pour y faire face).
Vraiment, l'exemple suivant ne rend pas justice à la flexibilité. Il ne fait que copier la structure de la classe. La différence que vous devez garder à l'esprit ici, cependant, est que la classe a le contrôle total pour utiliser tout type de JSON qu'elle souhaite contrôler l'état de la classe entière (vous pouvez calculer des choses, etc.).
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);