Come eseguire il casting del tipo runtime in TypeScript?

Attualmente sto lavorando a un progetto typescript e mi piace molto l’inferenza di tipo che TypeScript porta in tavola. Tuttavia, durante il richiamo di oggetti da chiamate HTTP, posso lanciarli nel tipo desiderato, ottenere il completamento del codice e richiamare le funzioni su di essi per compilare il tempo , ma questi causano errori di runtime

Esempio:

class Person{ name: string; public giveName() { return this.name; } constructor(json: any) { this.name = json.name; } } var somejson = { 'name' : 'John' }; // Typically from AJAX call var john = (somejson); // The cast console.log(john.name); // 'John' console.log(john.giveName()); // 'undefined is not a function' 

Anche se questo si compila bene – e intellisense mi suggerisce di usare la funzione, dà un’eccezione di runtime. Una soluzione per questo potrebbe essere:

 var somejson = { 'name' : 'Ann' }; var ann = new Person(somejson); console.log(ann.name); // 'Ann' console.log(ann.giveName()); // 'Ann' 

Ma questo mi richiederà di creare costruttori per tutti i miei tipi. In particolare, quando si tratta di tipi ad albero e / o di raccolte provenienti dalla chiamata AJAX, è necessario scorrere tutti gli elementi e creare nuovamente un’istanza per ciascuno.

Quindi la mia domanda: c’è un modo più elegante per farlo? Cioè, gettare su un tipo e avere le funzioni prototipo disponibili per esso immediatamente?

Dai un’occhiata al JavaScript compilato e vedrai che il tipo asserzione (casting) scompare perché è solo per la compilazione. In questo momento stai dicendo al compilatore che l’object somejson è di tipo Person . Il compilatore ti crede, ma in questo caso non è vero.

Quindi questo problema è un problema di JavaScript in fase di esecuzione.

L’objective principale per farlo funzionare è in qualche modo dire a JavaScript quale sia la relazione tra le classi. Così…

  1. Trova un modo per descrivere la relazione tra le classi.
  2. Creare qualcosa per mappare automaticamente JSON alle classi in base a questi dati di relazione.

Ci sono molti modi per risolverlo, ma offrirò un esempio in cima alla mia testa. Questo dovrebbe aiutare a descrivere ciò che deve essere fatto.

Diciamo che abbiamo questa class:

 class Person { name: string; child: Person; public giveName() { return this.name; } } 

E questi dati JSON:

 { name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } } 

Per mappare questo automaticamente come istanze di Person , dobbiamo dire al JavaScript come sono correlati i tipi. Non possiamo usare le informazioni sul tipo TypeScript perché lo perderemo una volta compilato. Un modo per farlo è avere una proprietà statica sul tipo che descrive questo. Per esempio:

 class Person { static relationships = { child: Person }; name: string; child: Person; public giveName() { return this.name; } } 

Quindi ecco un esempio di una funzione riutilizzabile che gestisce la creazione degli oggetti per noi sulla base di questi dati di relazione:

 function createInstanceFromJson(objType: { new(): T; }, json: any) { const newObj = new objType(); const relationships = objType["relationships"] || {}; for (const prop in json) { if (json.hasOwnProperty(prop)) { if (newObj[prop] == null) { if (relationships[prop] == null) { newObj[prop] = json[prop]; } else { newObj[prop] = createInstanceFromJson(relationships[prop], json[prop]); } } else { console.warn(`Property ${prop} not set because it already existed on the object.`); } } } return newObj; } 

Ora il seguente codice funzionerà:

 const someJson = { name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } }; const person = createInstanceFromJson(Person, someJson); console.log(person.giveName()); // John console.log(person.child.giveName()); // Sarah console.log(person.child.child.giveName()); // Jacob 

Terreno di gioco

Idealmente , il modo migliore sarebbe utilizzare qualcosa che in realtà legge il codice TypeScript e crea un object che mantiene la relazione tra le classi. In questo modo non è necessario mantenere manualmente le relazioni e preoccuparsi delle modifiche al codice. Ad esempio, in questo momento il codice di refactoring è un po ‘un rischio con questa configurazione. Non sono sicuro che esista qualcosa del genere al momento, ma è sicuramente ansible.

Soluzione alternativa

Mi sono appena reso conto che ho già risposto a una domanda simile con una soluzione leggermente diversa (che non coinvolge però i dati nidificati). Puoi leggerlo qui per qualche altra idea:

Istanza di class JSON to TypeScript?

Il prototipo della class può essere influenzato in modo dinamico sull’object:

 function cast(obj: any, cl: { new(...args): T }): T { obj.__proto__ = cl.prototype; return obj; } var john = cast(/* somejson */, Person); 

Vedi la documentazione di __proto__ qui .