Estas são algumas fotos rápidas para mostrar algumas maneiras diferentes. Eles não são de forma alguma "completos" e, como um aviso, não acho que seja uma boa ideia fazê-lo dessa maneira. Além disso, o código não está muito limpo, pois eu o digitei rapidamente.
Também como observação: é claro que as classes desserializáveis precisam ter construtores padrão, como é o caso em todos os outros idiomas em que tenho conhecimento de qualquer tipo de desserialização. Obviamente, o Javascript não reclamará se você chamar um construtor não padrão sem argumentos, mas é melhor que a classe esteja preparada para isso (além disso, não seria realmente a "maneira de escrever um texto").
Opção 1: nenhuma informação de tempo de execução
O problema com essa abordagem é principalmente que o nome de qualquer membro deve corresponder à sua classe. O que limita automaticamente a um membro do mesmo tipo por classe e quebra várias regras de boas práticas. Eu recomendo fortemente contra isso, mas apenas liste aqui porque foi o primeiro "rascunho" quando escrevi esta resposta (e é também por isso que os nomes são "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);
Opção # 2: a propriedade name
Para se livrar do problema na opção 1, precisamos ter algum tipo de informação sobre o tipo de nó no objeto JSON. O problema é que, no Typescript, essas coisas são construções em tempo de compilação e precisamos delas em tempo de execução - mas os objetos de tempo de execução simplesmente não têm conhecimento de suas propriedades até serem definidas.
Uma maneira de fazer isso é conscientizando as classes de seus nomes. Você também precisa dessa propriedade no JSON. Na verdade, você só precisa dele no 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);
Opção nº 3: declarando explicitamente os tipos de membros
Como mencionado acima, as informações de tipo dos membros da classe não estão disponíveis no tempo de execução - ou seja, a menos que as disponibilizemos. Só precisamos fazer isso para membros não primitivos e estamos prontos para:
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);
Opção 4: A maneira detalhada, mas organizada
Atualização 01/03/2016: Como o @GameAlchemist apontou nos comentários ( idéia , implementação ), a partir do Typescript 1.7, a solução descrita abaixo pode ser escrita de uma maneira melhor usando decoradores de classe / propriedade.
A serialização é sempre um problema e, na minha opinião, a melhor maneira é apenas a mais curta. De todas as opções, é isso que eu prefiro, porque o autor da classe tem controle total sobre o estado dos objetos desserializados. Se eu tivesse que adivinhar, diria que todas as outras opções, mais cedo ou mais tarde, causarão problemas (a menos que o Javascript tenha uma maneira nativa de lidar com isso).
Realmente, o exemplo a seguir não faz justiça à flexibilidade. Realmente apenas copia a estrutura da classe. A diferença que você deve ter em mente aqui, porém, é que a classe tem controle total para usar qualquer tipo de JSON que deseja controlar o estado de toda a classe (você pode calcular as coisas 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);