Come faccio a trasmettere un object JSON a una class typescript

Ho letto un object JSON da un server REST remoto. Questo object JSON ha tutte le proprietà di una class typescript (in base alla progettazione). Come faccio a trasmettere l’object JSON ricevuto a un tipo var?

Non voglio popolare una variabile typescript (vale a dire avere un costruttore che accetta questo object JSON). È grande e copiare tutto attraverso l’object secondario per object secondario e proprietà per proprietà richiederebbe molto tempo.

Aggiornamento: puoi comunque lanciarlo su un’interfaccia dattiloscritta!

Non è ansible eseguire il cast di un risultato plain-old-JavaScript da una richiesta Ajax in un’istanza di class JavaScript / TypeScript prototipo. Esistono numerose tecniche per farlo e in genere implicano la copia dei dati. A meno che non si crei un’istanza della class, non avrà alcun metodo o proprietà. Rimarrà un semplice object JavaScript.

Mentre se si trattasse solo di dati, si potrebbe semplicemente eseguire un cast su un’interfaccia (poiché si tratta di una struttura puramente di compilazione del tempo), ciò richiederebbe l’utilizzo di una class TypeScript che utilizza l’istanza di dati ed esegue operazioni con tali dati.

Alcuni esempi di copia dei dati:

  1. Copia di oggetti JSON AJAX nell’object esistente
  2. Analizza la stringa JSON in un prototipo di object particolare in JavaScript

In sostanza, dovresti solo:

var d = new MyRichObject(); d.copyInto(jsonResult); 

Ho avuto lo stesso problema e ho trovato una libreria che fa il lavoro: https://github.com/pleerock/class-transformsr .

Funziona così:

  let jsonObject = response.json() as Object; let fooInstance = plainToClass(Models.Foo, jsonObject); return fooInstance; 

Supporta i bambini nidificati ma devi decorare il membro della tua class.

In TypeScript puoi fare un’asserzione di tipo usando un’interfaccia e generici come questi:

 var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json"); var locations: Array = JSON.parse(json).location; 

Dove ILocationMap descrive la forma dei tuoi dati. Il vantaggio di questo metodo è che il tuo JSON potrebbe contenere più proprietà ma la forma soddisfa le condizioni dell’interfaccia.

Spero che aiuti!

Ho trovato un articolo molto interessante sul casting generico di JSON in una class Typescript:

http://cloudmark.github.io/Json-Mapping/

Finisci con il seguente codice:

 let example = { "name": "Mark", "surname": "Galea", "age": 30, "address": { "first-line": "Some where", "second-line": "Over Here", "city": "In This City" } }; MapUtils.deserialize(Person, example); // custom class 

Se stai usando ES6, prova questo:

 class Client{ name: string displayName(){ console.log(this.name) } } service.getClientFromAPI().then(clientData => { // Here the client data from API only have the "name" field // If we want to use the Client class methods on this data object we need to: let clientWithType = Object.assign(new Client(), clientData) clientWithType.displayName() }) 

Ma in questo modo non funzionerà sull’object nido, purtroppo.

Supponendo che JSON abbia le stesse proprietà della tua class typescript, non devi copiare le tue proprietà JSON nel tuo object typescript. Dovrai semplicemente build il tuo object Typescript passando i dati json nel costruttore.

Nel tuo callback ajax, ricevi una società:

 onReceiveCompany( jsonCompany : any ) { let newCompany = new Company( jsonCompany ); // call the methods on your newCompany object ... } 

In per fare questo lavoro:

1) Aggiungi un costruttore nella tua class Typescript che prende i dati JSON come parametro. In questo costruttore estendi il tuo object json con jQuery, in questo modo: $.extend( this, jsonData) . $ .extend consente di mantenere i prototipi di javascript durante l’aggiunta delle proprietà dell’object json.

2) Nota che dovrai fare lo stesso per gli oggetti collegati. Nel caso di Employees nell’esempio, si crea anche un costruttore che prende la parte dei dati JSON per i dipendenti. Si chiama $ .map per tradurre gli impiegati json in oggetti Dipendente typescript.

 export class Company { Employees : Employee[]; constructor( jsonData: any ) { $.extend( this, jsonData); if ( jsonData.Employees ) this.Employees = $.map( jsonData.Employees , (emp) => { return new Employee ( emp ); }); } } export class Employee { name: string; salary: number; constructor( jsonData: any ) { $.extend( this, jsonData); } } 

Questa è la soluzione migliore che ho trovato quando si tratta di classi Typescript e oggetti json.

Nel mio caso funziona. Ho usato le funzioni Object.assign (target, sources …) . Innanzitutto, la creazione dell’object corretto, quindi copia i dati dall’object json sulla destinazione. Esempio:

 let u:User = new User(); Object.assign(u , jsonUsers); 

E un esempio più avanzato di utilizzo. Un esempio che utilizza la matrice.

 this.someService.getUsers().then((users: User[]) => { this.users = []; for (let i in users) { let u:User = new User(); Object.assign(u , users[i]); this.users[i] = u; console.log("user:" + this.users[i].id); console.log("user id from function(test it work) :" + this.users[i].getId()); } }); export class User { id:number; name:string; fullname:string; email:string; public getId(){ return this.id; } } 

TLDR: una fodera

 // This assumes your constructor method will assign properties from the arg. .map((instanceData: MyClass) => new MyClass(instanceData)); 

La risposta dettagliata

Non consiglierei l’approccio Object.assign, poiché può annidare in modo inappropriato l’istanza della class con proprietà irrilevanti (così come con le chiusure definite) che non sono state dichiarate all’interno della class stessa.

Nella class in cui stai cercando di deserializzare, assicurerei che tutte le proprietà che vuoi deserializzare siano definite (null, array vuoto, ecc.). Definendo le proprietà con i valori iniziali si espone la loro visibilità quando si tenta di iterare i membri della class per assegnare valori a (vedi metodo deserialize di seguito).

 export class Person { public name: string = null; public favoriteSites: string[] = []; private age: number = null; private id: number = null; private active: boolean; constructor(instanceData?: Person) { if (instanceData) { this.deserialize(instanceData); } } private deserialize(instanceData: Person) { // Note this.active will not be listed in keys since it's declared, but not defined const keys = Object.keys(this); for (const key of keys) { if (instanceData.hasOwnProperty(key)) { this[key] = instanceData[key]; } } } } 

Nell’esempio sopra, ho semplicemente creato un metodo di deserializzazione. In un esempio reale, lo avrei centralizzato in una class base o in un metodo di servizio riutilizzabili.

Ecco come utilizzare questo in qualcosa come un http resp …

 this.http.get(ENDPOINT_URL) .map(res => res.json()) .map((resp: Person) => new Person(resp) ) ); 

Se tslint / ide si lamenta che il tipo di argomento non è compatibile, basta convertire l’argomento nello stesso tipo usando parentesi angolari , esempio:

 const person = new Person( { name: 'John', age: 35, id: 1 }); 

Se si dispone di membri della class di un tipo specifico (ovvero un’istanza di un’altra class), è ansible convertirli in istanze digitate tramite i metodi getter / setter.

 export class Person { private _acct: UserAcct = null; private _tasks: Task[] = []; // ctor & deserialize methods... public get acct(): UserAcct { return this.acct; } public set acct(acctData: UserAcct) { this._acct = new UserAcct(acctData); } public get tasks(): Task[] { return this._tasks; } public set tasks(taskData: Task[]) { this._tasks = taskData.map(task => new Task(task)); } } 

L’esempio precedente deserializza sia l’acct che l’elenco di attività nelle rispettive istanze di class.

Non c’è ancora nulla da verificare automaticamente se l’object JSON ricevuto dal server ha le proprietà dell’interfaccia typescript attese (leggi è conforms a). Ma puoi usare le guardie tipo definite dall’utente

Considerando la seguente interfaccia e un object json stupido (potrebbe essere stato di qualsiasi tipo):

 interface MyInterface { key: string; } const json: object = { "key": "value" } 

Tre possibili modi:

A. Asserzione di tipo o semplice cast statico posto dopo la variabile

 const myObject: MyInterface = json as MyInterface; 

B. Semplice fusione statica, prima della variabile e tra i diamanti

 const myObject: MyInterface = json; 

C. Advanced dynamic cast, ti controlli la struttura dell’object

 function isMyInterface(json: any): json is MyInterface { // silly condition to consider json as conform for MyInterface return typeof json.key === "string"; } if (isMyInterface(json)) { console.log(json.key) } else { throw new Error(`Expected MyInterface, got '${json}'.`); } 

Puoi giocare con questo esempio qui

Notare che la difficoltà qui è scrivere la funzione isMyInterface . Spero che TS aggiunga un decoratore prima o poi per esportare la tipizzazione complessa nel runtime e lasciare che il runtime controlli la struttura dell’object quando necessario. Per ora, è ansible utilizzare un validatore dello schema JSON il cui scopo è approssimativamente lo stesso O questo generatore di funzioni di controllo del tipo di runtime

Mentre non è un casting per dire; Ho trovato https://github.com/JohnWhiteTB/TypedJSON per essere un’alternativa utile.

 @JsonObject class Person { @JsonMember firstName: string; @JsonMember lastName: string; public getFullname() { return this.firstName + " " + this.lastName; } } var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person); person instanceof Person; // true person.getFullname(); // "John Doe" 

Ho creato un semplice strumento per fare questo https://beshanoe.github.io/json2ts/ A differenza di json2ts.com funziona direttamente nel browser e non invia i tuoi dati a server sconosciuti. Inoltre ha più impostazioni. Lavorerò per migliorare la sua funzionalità

Una vecchia domanda con risposte per lo più corrette, ma non molto efficaci. Questo ciò che propongo:

Creare una class base che contenga metodi init () e metodi cast statici (per un singolo object e un array). I metodi statici potrebbero essere ovunque; la versione con la class base e init () consente in seguito facili estensioni.

 export class ContentItem { // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem) static castAs(doc: T, proto: typeof ContentItem): T { // if we already have the correct class skip the cast if (doc instanceof proto) { return doc; } // create a new object (create), and copy over all properties (assign) const d: T = Object.create(proto.prototype); Object.assign(d, doc); // reason to extend the base class - we want to be able to call init() after cast d.init(); return d; } // another method casts an array static castAllAs(docs: T[], proto: typeof ContentItem): T[] { return docs.map(d => ContentItem.castAs(d, proto)); } init() { } } 

Meccaniche simili (con assign () ) sono state menzionate nel post di @ Adam111p. Solo un altro (più completo) modo di farlo. @Timothy Perez è critico su assign () , ma secondo me è del tutto appropriato qui.

Implementare una class derivata (la vera):

 import { ContentItem } from './content-item'; export class SubjectArea extends ContentItem { id: number; title: string; areas: SubjectArea[]; // contains embedded objects depth: number; // method will be unavailable unless we use cast lead(): string { return '. '.repeat(this.depth); } // in case we have embedded objects, call cast on them here init() { if (this.areas) { this.areas = ContentItem.castAllAs(this.areas, SubjectArea); } } } 

Ora possiamo lanciare un object recuperato dal servizio:

 const area = ContentItem.castAs(docFromREST, SubjectArea); 

Tutta la gerarchia degli oggetti SubjectArea avrà una class corretta.

Un caso d’uso / esempio; creare un servizio angular (di nuovo una class di base astratta):

 export abstract class BaseService { BASE_URL = 'http://host:port/'; protected abstract http: Http; abstract path: string; abstract subClass: typeof ContentItem; cast(source: T): T { return ContentItem.castAs(source, this.subClass); } castAll(source: T[]): T[] { return ContentItem.castAllAs(source, this.subClass); } constructor() { } get(): Promise { const value = this.http.get(`${this.BASE_URL}${this.path}`) .toPromise() .then(response => { const items: T[] = this.castAll(response.json()); return items; }); return value; } } 

L’utilizzo diventa molto semplice; creare un servizio di Area:

 @Injectable() export class SubjectAreaService extends BaseService { path = 'area'; subClass = SubjectArea; constructor(protected http: Http) { super(); } } 

Il metodo get () del servizio restituirà una Promessa di un array già lanciato come oggetti SubjectArea (intera gerarchia)

Ora dì, abbiamo un’altra class:

 export class OtherItem extends ContentItem {...} 

La creazione di un servizio che recupera dati e trasmette alla class corretta è semplice come:

 @Injectable() export class OtherItemService extends BaseService { path = 'other'; subClass = OtherItem; constructor(protected http: Http) { super(); } } 

Ho usato questa libreria qui: https://github.com/pleerock/class-transformsr

  

Implementazione:

 private async getClassTypeValue() { const value = await plainToClass(ProductNewsItem, JSON.parse(response.data)); } 

A volte dovrai analizzare i valori JSON per plainToClass per capire che si tratta di dati formattati JSON

Una cosa che abbiamo fatto perché siamo un negozio .NET è creare questo strumento ( https://github.com/Centeva/TypeScripter ).

Costruisce classi di dattiloscritti dalle nostre DLL. Tutto quello che dobbiamo fare è convogliare la nostra risposta json nella class come parametro. Funziona bene per noi.

Non vedo alcun riferimento a JSON-typescript-mapper: https://www.npmjs.com/package/json-typescript-mapper . Sembra una combinazione del ritrovamento di @ PhilipMiglinci e la risposta di @ Pak, per quanto posso dire.

Questa è un’opzione semplice e davvero buona

 let person = "{"name":"Sam","Age":"30"}"; const jsonParse: ((key: string, value: any) => any) | undefined = undefined; let objectConverted = JSON.parse(textValue, jsonParse); 

E poi avrai

 objectConverted.name