Estas son algunas tomas rápidas para mostrar algunas formas diferentes. De ninguna manera son "completos" y, como descargo de responsabilidad, no creo que sea una buena idea hacerlo así. Además, el código no está demasiado limpio, ya que lo escribí todo bastante rápido.
También como una nota: por supuesto, las clases deserializables deben tener constructores predeterminados, como es el caso en todos los demás idiomas donde estoy al tanto de cualquier deserialización. Por supuesto, Javascript no se quejará si llama a un constructor no predeterminado sin argumentos, pero es mejor que la clase esté preparada para eso (además, realmente no sería la "forma de tipografía").
Opción # 1: no hay información de tiempo de ejecución
El problema con este enfoque es principalmente que el nombre de cualquier miembro debe coincidir con su clase. Lo que lo limita automáticamente a un miembro del mismo tipo por clase y rompe varias reglas de buenas prácticas. Le recomiendo encarecidamente que no lo haga, pero solo enumérelo aquí porque fue el primer "borrador" cuando escribí esta respuesta (que también explica por qué los nombres son "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);
Opción # 2: la propiedad de nombre
Para deshacernos del problema en la opción # 1, necesitamos tener algún tipo de información de qué tipo es un nodo en el objeto JSON. El problema es que en Typecript, estas cosas son construcciones en tiempo de compilación y las necesitamos en tiempo de ejecución, pero los objetos de tiempo de ejecución simplemente no tienen conocimiento de sus propiedades hasta que se establecen.
Una forma de hacerlo es haciendo que las clases conozcan sus nombres. Sin embargo, también necesita esta propiedad en JSON. En realidad, solo lo necesitas en el 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);
Opción n. ° 3: indicar explícitamente los tipos de miembros
Como se indicó anteriormente, la información de tipo de los miembros de la clase no está disponible en tiempo de ejecución, es decir, a menos que la hagamos disponible. Solo necesitamos hacer esto para los miembros no primitivos y estamos listos para comenzar:
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);
Opción # 4: la forma detallada, pero ordenada
Actualización 01/03/2016: Como @GameAlchemist señaló en los comentarios ( idea , implementación ), a partir de Typecript 1.7, la solución que se describe a continuación se puede escribir de una mejor manera utilizando decoradores de clase / propiedad.
La serialización siempre es un problema y, en mi opinión, la mejor manera es la que no es la más corta. De todas las opciones, esto es lo que preferiría porque el autor de la clase tiene control total sobre el estado de los objetos deserializados. Si tuviera que adivinar, diría que todas las demás opciones, tarde o temprano, lo meterán en problemas (a menos que Javascript presente una forma nativa de lidiar con esto).
Realmente, el siguiente ejemplo no le hace justicia a la flexibilidad. Realmente solo copia la estructura de la clase. Sin embargo, la diferencia que debe tener en cuenta aquí es que la clase tiene el control total para usar cualquier tipo de JSON que quiera controlar el estado de toda la clase (puede calcular cosas, 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);