Questi sono alcuni scatti rapidi per mostrare alcuni modi diversi. Non sono affatto "completi" e come disclaimer, non penso sia una buona idea farlo in questo modo. Inoltre, il codice non è troppo pulito poiché l'ho appena digitato insieme piuttosto rapidamente.
Anche come nota: ovviamente le classi deserializzabili devono avere costruttori predefiniti come in tutte le altre lingue in cui sono a conoscenza della deserializzazione di qualsiasi tipo. Naturalmente, Javascript non si lamenterà se chiami un costruttore non predefinito senza argomenti, ma la classe sarà meglio preparata per questo (in più, non sarebbe davvero il "modo tipografico").
Opzione n. 1: nessuna informazione di runtime
Il problema con questo approccio è principalmente che il nome di qualsiasi membro deve corrispondere alla sua classe. Ciò ti limita automaticamente a un membro dello stesso tipo per classe e infrange diverse regole di buona pratica. Sconsiglio vivamente questo, ma lo elencherò qui perché è stata la prima "bozza" quando ho scritto questa risposta (che è anche il motivo per cui i nomi sono "Foo" ecc.).
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);
Opzione n. 2: la proprietà name
Per eliminare il problema nell'opzione n. 1, è necessario disporre di alcune informazioni sul tipo di nodo nell'oggetto JSON. Il problema è che in Typescript queste cose sono costrutti in fase di compilazione e ne abbiamo bisogno in fase di esecuzione - ma gli oggetti in fase di esecuzione semplicemente non sono consapevoli delle loro proprietà fino a quando non vengono impostati.
Un modo per farlo è rendere le classi consapevoli dei loro nomi. Tuttavia, questa proprietà è necessaria anche in JSON. In realtà, ne hai solo bisogno nel 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);
Opzione n. 3: indicazione esplicita dei tipi di membri
Come indicato sopra, le informazioni sul tipo dei membri della classe non sono disponibili in fase di esecuzione - a meno che non le rendiamo disponibili. Abbiamo solo bisogno di fare questo per i membri non primitivi e siamo a posto:
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);
Opzione n. 4: il modo dettagliato, ma pulito
Aggiornamento 01/03/2016: Come sottolineato da @GameAlchemist nei commenti ( idea , implementazione ), a partire da Typescript 1.7, la soluzione descritta di seguito può essere scritta in modo migliore usando decoratori di classi / proprietà.
La serializzazione è sempre un problema e, a mio avviso, il modo migliore è un modo che non è il più breve. Tra tutte le opzioni, questo è ciò che preferirei perché l'autore della classe ha il pieno controllo sullo stato degli oggetti deserializzati. Se dovessi indovinare, direi che tutte le altre opzioni, prima o poi, ti metteranno nei guai (a meno che Javascript non abbia un modo nativo per gestirlo).
Davvero, il seguente esempio non rende giustizia alla flessibilità. Copia semplicemente la struttura della classe. La differenza che devi tenere a mente qui, però, è che la classe ha il pieno controllo per usare qualsiasi tipo di JSON che vuole controllare lo stato dell'intera classe (puoi calcolare cose ecc.).
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);