Come posso inizializzare un oggetto TypeScript con un oggetto JSON


199

Ricevo un oggetto JSON da una chiamata AJAX a un server REST. Questo oggetto ha nomi di proprietà che corrispondono alla mia classe TypeScript (questo è il seguito di questa domanda ).

Qual è il modo migliore per inizializzarlo? Non credo che questo funzionerà perché la classe (e oggetto JSON) hanno i membri che sono liste di oggetti e componenti che sono le classi e le classi hanno i membri che sono liste e / o classi.

Ma preferirei un approccio che cerca i nomi dei membri e li assegna attraverso, creando elenchi e istanziando le classi secondo necessità, quindi non devo scrivere codice esplicito per ogni membro di ogni classe (ce n'è MOLTO!)


1
Perché l'hai chiesto di nuovo (poiché la risposta che ho fornito nell'altra domanda diceva che non avrebbe funzionato e che si trattava di copiare le proprietà in un oggetto esistente)?
WiredPrairie,


3
@WiredPrairie questa domanda è diversa, mi chiede se posso percorrere le proprietà una per una e assegnarle. L'altra domanda era se potessi lanciarlo.
David Thielen,

1
@WiredPrairie cont: Se continui a immergerti nelle proprietà fino a quando non raggiungi solo i tipi primitivi, allora questi possono essere assegnati.
David Thielen,

2
Sta ancora copiando tutti i valori proprio come ho suggerito che avresti dovuto fare. Non esiste un nuovo modo per farlo in TypeScript in quanto è un progetto fondamentale di JavaScript. Per oggetti di grandi dimensioni, potresti non voler copiare alcun valore e semplicemente "agire" sulla struttura dei dati.
WiredPrairie,

Risposte:


189

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);

12
L'opzione n. 4 è quella che definirei una strada ragionevole da percorrere. Devi ancora scrivere il codice di deserializzazione, ma è nella stessa classe e completamente controllabile. Se vieni da Java, allora è paragonabile a dover scrivere equalso toStringmetodi (solo che di solito li hai generati automaticamente). Non dovrebbe essere troppo difficile scrivere un generatore per deserializese lo si desidera, ma non può essere l'automazione di runtime.
Ingo Bürk,

2
@ IngoBürk, so che farò questa domanda 2 anni dopo, ma come funzionerà su una serie di oggetti? Il codice di esempio riportato sopra funziona correttamente per l'oggetto JSON. come può essere utilizzato per array di oggetti?
Pratik Gaikwad,

2
Un'osservazione a margine: dal 1.7, (certamente più recente della tua risposta), dattiloscritto fornisce decoratori di classe / proprietà che consente di scrivere la 4a soluzione in modo più ordinato.
GameAlchemist,

1
La migliore documentazione che ho trovato è una risposta StackOverflow: stackoverflow.com/a/29837695/856501 . Ho usato i decoratori in un mio progetto, e anche se vorrei alcune altre caratteristiche, devo dire che funzionano come un fascino.
GameAlchemist,

2
Non salterei sui decoratori per un progetto di produzione ancora - tieni presente che sono ancora una caratteristica sperimentale. Non baserei il codice del mondo reale su "esperimenti" perché, per quanto ci riguarda, potrebbero essere spariti nella prossima versione e dovresti riscrivere un sacco di codice o rimanere bloccato per sempre su una vecchia versione TS. Solo il mio $ 0,02
RVP

35

puoi usare Object.assignNon so quando è stato aggiunto, attualmente sto usando Typescript 2.0.2 e questa sembra essere una funzionalità ES6.

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

ecco HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

ecco cosa dice Chrome

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

così puoi vedere che non fa l'assegnazione in modo ricorsivo


2
Quindi, in sostanza, è questo: Object.assign. Perché allora abbiamo due risposte simili a un lessico sopra questa?
phil294,

18
@Blauhim Perché Object.assignnon funziona in modo ricorsivo e non crea un'istanza dei tipi di oggetto corretti, lasciando i valori come Objectistanze. Mentre va bene per compiti banali, la serializzazione di tipo complesso non è possibile con esso. Ad esempio, se una proprietà di classe è di un tipo di classe personalizzato, JSON.parse+ Object.assigncreerà un'istanza di tale proprietà Object. Gli effetti collaterali includono metodi e accessori mancanti.
John Weisz,

@JohnWeisz la classe di livello superiore di assegnazione degli oggetti ha il tipo corretto, e ho menzionato la cosa ricorsiva in questo ... che ha detto, YMMV, e quelli potrebbero essere dei rompicapo.
xenoterracide,

Citato direttamente dalla domanda: "la classe ha membri che sono liste di oggetti e membri che sono classi, e quelle classi hanno membri che sono liste e / o classi [...] Preferirei un approccio che cerchi il membro i nomi e li assegna attraverso, creando elenchi e istanze delle classi in base alle necessità, quindi non devo scrivere codice esplicito per ogni membro in ogni classe " - il che non è il caso Object.assign, in cui si tratta ancora di scrivere istanze nidificate da mano. Questo approccio va bene per oggetti molto semplici di livello tutorial, ma non per un uso reale.
John Weisz,

@JohnWeisz certo, per lo più ha risposto con questo perché non era in nessuna risposta e sembrava semplice per alcuni casi d'uso. Sono certo che potrebbe anche essere usato in combinazione con altre risposte come la riflessione, al fine di fare quello che stai cercando. L'ho anche scritto in parte per ricordarmelo in seguito. Guardando queste risposte e avendo usato e scritto librerie molto più potenti non sembra esserci nulla disponibile per un "uso reale".
xenoterracide,

34

TLDR: TypedJSON (prova funzionale del concetto)


La radice della complessità di questo problema è che dobbiamo deserializzare JSON in fase di esecuzione utilizzando le informazioni sul tipo che esistono solo al momento della compilazione . Ciò richiede che le informazioni sul tipo siano in qualche modo rese disponibili in fase di esecuzione.

Fortunatamente, questo può essere risolto in modo molto elegante e robusto con decoratori e ReflectDecorator :

  1. Utilizzare i decoratori di proprietà su proprietà soggette a serializzazione, per registrare informazioni sui metadati e archiviare tali informazioni da qualche parte, ad esempio sul prototipo di classe
  2. Invia queste informazioni sui metadati a un inizializzatore ricorsivo (deserializzatore)

 

Informazioni sul tipo di registrazione

Con una combinazione di ReflectDecorator e decoratori di proprietà, le informazioni sul tipo possono essere facilmente registrate su una proprietà. Un'attuazione rudimentale di questo approccio sarebbe:

function JsonMember(target: any, propertyKey: string) {
    var metadataFieldKey = "__propertyTypes__";

    // Get the already recorded type-information from target, or create
    // empty object if this is the first property.
    var propertyTypes = target[metadataFieldKey] || (target[metadataFieldKey] = {});

    // Get the constructor reference of the current property.
    // This is provided by TypeScript, built-in (make sure to enable emit
    // decorator metadata).
    propertyTypes[propertyKey] = Reflect.getMetadata("design:type", target, propertyKey);
}

Per ogni data proprietà, il frammento sopra aggiungerà un riferimento della funzione di costruzione della proprietà alla __propertyTypes__proprietà nascosta sul prototipo di classe. Per esempio:

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

Ed è tutto, abbiamo le informazioni richieste sul tipo in fase di esecuzione, che ora possono essere elaborate.

 

Elaborazione delle informazioni sul tipo

Per prima cosa dobbiamo ottenere Objectun'istanza usando JSON.parse- dopodiché, possiamo iterare sugli imperi in __propertyTypes__(raccolti sopra) e creare un'istanza delle proprietà richieste di conseguenza. È necessario specificare il tipo di oggetto root, in modo che il deserializzatore abbia un punto di partenza.

Ancora una volta, un'implementazione semplice e morta di questo approccio sarebbe:

function deserialize<T>(jsonObject: any, Constructor: { new (): T }): T {
    if (!Constructor || !Constructor.prototype.__propertyTypes__ || !jsonObject || typeof jsonObject !== "object") {
        // No root-type with usable type-information is available.
        return jsonObject;
    }

    // Create an instance of root-type.
    var instance: any = new Constructor();

    // For each property marked with @JsonMember, do...
    Object.keys(Constructor.prototype.__propertyTypes__).forEach(propertyKey => {
        var PropertyType = Constructor.prototype.__propertyTypes__[propertyKey];

        // Deserialize recursively, treat property type as root-type.
        instance[propertyKey] = deserialize(jsonObject[propertyKey], PropertyType);
    });

    return instance;
}
var json = '{ "name": "John Doe", "language": { "name": "en", "level": 5 } }';
var person: Person = deserialize(JSON.parse(json), Person);

L'idea sopra ha un grande vantaggio di deserializzare in base ai tipi previsti (per valori complessi / oggetto), anziché ciò che è presente in JSON. Se Personè previsto a, allora Personviene creata un'istanza. Con alcune misure di sicurezza aggiuntive in atto per tipi e matrici primitivi, questo approccio può essere reso sicuro, che resiste a qualsiasi JSON dannoso.

 

Custodie per bordi

Tuttavia, se ora sei felice che la soluzione sia così semplice, ho alcune brutte notizie: c'è un gran numero di casi limite che devono essere curati. Solo alcuni dei quali sono:

  • Matrici ed elementi di matrice (specialmente nelle matrici nidificate)
  • Polimorfismo
  • Classi astratte e interfacce
  • ...

Se non vuoi giocherellare con tutti questi (scommetto che non lo fai), sarei felice di raccomandare una versione sperimentale funzionante di una prova di concetto utilizzando questo approccio, TypedJSON - che ho creato per affrontare questo esatto problema, un problema che devo affrontare ogni giorno.

A causa del modo in cui i decoratori sono ancora considerati sperimentali, non consiglierei di usarlo per l'uso in produzione, ma finora mi è servito bene.


TypedJSON ha funzionato alla grande; grazie mille per il riferimento.
Neil,

Ottimo lavoro, hai trovato una soluzione molto elegante a un problema che mi ha turbato per un po '. Seguirò da vicino il tuo progetto!
John Strickler,

12

Ho usato questo ragazzo per fare il lavoro: https://github.com/weichx/cerialize

È molto semplice ma potente. Supporta:

  • Serializzazione e deserializzazione di un intero albero di oggetti.
  • Proprietà permanenti e transitorie sullo stesso oggetto.
  • Ganci per personalizzare la logica (de) di serializzazione.
  • Può (de) serializzare in un'istanza esistente (ottima per Angular) o generare nuove istanze.
  • eccetera.

Esempio:

class Tree {
  @deserialize public species : string; 
  @deserializeAs(Leaf) public leafs : Array<Leaf>;  //arrays do not need extra specifications, just a type.
  @deserializeAs(Bark, 'barkType') public bark : Bark;  //using custom type and custom key name
  @deserializeIndexable(Leaf) public leafMap : {[idx : string] : Leaf}; //use an object as a map
}

class Leaf {
  @deserialize public color : string;
  @deserialize public blooming : boolean;
  @deserializeAs(Date) public bloomedAt : Date;
}

class Bark {
  @deserialize roughness : number;
}

var json = {
  species: 'Oak',
  barkType: { roughness: 1 },
  leafs: [ {color: 'red', blooming: false, bloomedAt: 'Mon Dec 07 2015 11:48:20 GMT-0500 (EST)' } ],
  leafMap: { type1: { some leaf data }, type2: { some leaf data } }
}
var tree: Tree = Deserialize(json, Tree);

6

Ho creato uno strumento che genera interfacce TypeScript e una "mappa dei tipi" di runtime per eseguire il controllo dei tipi di runtime rispetto ai risultati di JSON.parse: ts.quicktype.io

Ad esempio, dato questo JSON:

{
  "name": "David",
  "pets": [
    {
      "name": "Smoochie",
      "species": "rhino"
    }
  ]
}

quicktype produce la seguente interfaccia TypeScript e la mappa dei tipi:

export interface Person {
    name: string;
    pets: Pet[];
}

export interface Pet {
    name:    string;
    species: string;
}

const typeMap: any = {
    Person: {
        name: "string",
        pets: array(object("Pet")),
    },
    Pet: {
        name: "string",
        species: "string",
    },
};

Quindi controlliamo il risultato di JSON.parsecontro la mappa dei tipi:

export function fromJson(json: string): Person {
    return cast(JSON.parse(json), object("Person"));
}

Ho lasciato un po 'di codice, ma puoi provare quicktype per i dettagli.


1
Dopo aver fatto molte ore di ricerca e aver provato le mie tecniche di analisi, posso dire che questa è un'ottima soluzione, principalmente perché i decoratori sono ancora sperimentali. * Il link originale è rotto per me; ma ts.quicktype.io funziona. * La conversione dello schema JSON in JSON è un buon primo passo.
LexieHankins,

3

Opzione n. 5: utilizzo dei costruttori di Typescript e jQuery.extend

Questo sembra essere il metodo più gestibile: aggiungi un costruttore che accetta come parametro la struttura json ed estende l'oggetto json. In questo modo è possibile analizzare una struttura json nell'intero modello dell'applicazione.

Non è necessario creare interfacce o elencare le proprietà nel costruttore.

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // apply the same principle to linked objects:
        if ( jsonData.Employees )
            this.Employees = jQuery.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }

    calculateSalaries() : void { .... }
}

export class Employee
{
    name: string;
    salary: number;
    city: string;

    constructor( jsonData: any )
    {
        jQuery.extend( this, jsonData);

        // case where your object's property does not match the json's:
        this.city = jsonData.town;
    }
}

Nel tuo callback Ajax dove ricevi una società per calcolare gli stipendi:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

da dove $.extendvieni?
whale_steward,

@whale_steward Suppongo che l'autore si riferisca alla libreria jQuery. Nel mondo JavaScript, "$" è molto spesso qualcuno che utilizza jQuery.
Nick Roth,

come importarlo? basta includerlo su HTML capo è sufficiente?
whale_steward,

Sì, aggiorno la risposta per sostituire $ con jQuery. importare jQuery.js nella testata html e installare e aggiungere @ types / jquery nella sezione package.json, devDependencies.
Anthony Brenelière,

1
Nota che in Javascript dovresti farlo Object.assign, il che rimuove questa dipendenza da jQuery.
Léon Pelletier,

2

Per oggetti semplici, mi piace questo metodo:

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

Sfruttare la capacità di definire le proprietà nel costruttore consente di essere conciso.

Questo ti dà un oggetto tipizzato (rispetto a tutte le risposte che usano Object.assign o qualche variante, che ti dà un oggetto) e non richiede librerie o decoratori esterni.


1

La quarta opzione sopra descritta è un modo semplice e piacevole per farlo, che deve essere combinato con la seconda opzione nel caso in cui devi gestire una gerarchia di classi come ad esempio un elenco di membri che è una delle occorrenze di sottoclassi di un membro superclassista, ad esempio il direttore estende il membro o lo studente estende il membro. In tal caso, devi specificare il tipo di sottoclasse nel formato json


1

JQuery .extend fa questo per te:

var mytsobject = new mytsobject();

var newObj = {a:1,b:2};

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

1

il migliore che ho trovato a questo scopo è il trasformatore di classe. github.com/typestack/class-transformer

Ecco come lo usi:

Qualche lezione:

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}


import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
u = plainToClass(Foo, JSONobj);
 }

Se si utilizza anche le proprietà nidificate del decoratore @Type, verranno create.


0

Forse non è una soluzione reale, ma semplice:

interface Bar{
x:number;
y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

lavorare anche per dipendenze difficili !!!


9
Questo approccio in realtà non funziona come previsto. Se si ispezionano i risultati di runtime, bazsaranno di tipo Objecte non di tipo Bar.Funziona in questo semplice caso perché Barnon ha metodi (solo proprietà primitive). Se Baravesse un metodo simile isEnabled(), questo approccio fallirebbe dal momento che quel metodo non sarebbe nella stringa JSON serializzata.
Todd

0

Un'altra opzione che utilizza le fabbriche

export class A {

    id: number;

    date: Date;

    bId: number;
    readonly b: B;
}

export class B {

    id: number;
}

export class AFactory {

    constructor(
        private readonly createB: BFactory
    ) { }

    create(data: any): A {

        const createB = this.createB.create;

        return Object.assign(new A(),
            data,
            {
                get b(): B {

                    return createB({ id: data.bId });
                },
                date: new Date(data.date)
            });
    }
}

export class BFactory {

    create(data: any): B {

        return Object.assign(new B(), data);
    }
}

https://github.com/MrAntix/ts-deserialize

usare così

import { A, B, AFactory, BFactory } from "./deserialize";

// create a factory, simplified by DI
const aFactory = new AFactory(new BFactory());

// get an anon js object like you'd get from the http call
const data = { bId: 1, date: '2017-1-1' };

// create a real model from the anon js object
const a = aFactory.create(data);

// confirm instances e.g. dates are Dates 
console.log('a.date is instanceof Date', a.date instanceof Date);
console.log('a.b is instanceof B', a.b instanceof B);
  1. semplifica le tue lezioni
  2. iniezione disponibile alle fabbriche per flessibilità

0

Personalmente preferisco l'opzione n. 3 di @Ingo Bürk. E ho migliorato i suoi codici per supportare una matrice di dati complessi e una matrice di dati primitivi.

interface IDeserializable {
  getTypes(): Object;
}

class Utility {
  static deserializeJson<T>(jsonObj: object, classType: any): T {
    let instanceObj = new classType();
    let types: IDeserializable;
    if (instanceObj && instanceObj.getTypes) {
      types = instanceObj.getTypes();
    }

    for (var prop in jsonObj) {
      if (!(prop in instanceObj)) {
        continue;
      }

      let jsonProp = jsonObj[prop];
      if (this.isObject(jsonProp)) {
        instanceObj[prop] =
          types && types[prop]
            ? this.deserializeJson(jsonProp, types[prop])
            : jsonProp;
      } else if (this.isArray(jsonProp)) {
        instanceObj[prop] = [];
        for (let index = 0; index < jsonProp.length; index++) {
          const elem = jsonProp[index];
          if (this.isObject(elem) && types && types[prop]) {
            instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
          } else {
            instanceObj[prop].push(elem);
          }
        }
      } else {
        instanceObj[prop] = jsonProp;
      }
    }

    return instanceObj;
  }

  //#region ### get types ###
  /**
   * check type of value be string
   * @param {*} value
   */
  static isString(value: any) {
    return typeof value === "string" || value instanceof String;
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isNumber(value: any) {
    return typeof value === "number" && isFinite(value);
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isArray(value: any) {
    return value && typeof value === "object" && value.constructor === Array;
  }

  /**
   * check type of value be object
   * @param {*} value
   */
  static isObject(value: any) {
    return value && typeof value === "object" && value.constructor === Object;
  }

  /**
   * check type of value be boolean
   * @param {*} value
   */
  static isBoolean(value: any) {
    return typeof value === "boolean";
  }
  //#endregion
}

// #region ### Models ###
class Hotel implements IDeserializable {
  id: number = 0;
  name: string = "";
  address: string = "";
  city: City = new City(); // complex data
  roomTypes: Array<RoomType> = []; // array of complex data
  facilities: Array<string> = []; // array of primitive data

  // getter example
  get nameAndAddress() {
    return `${this.name} ${this.address}`;
  }

  // function example
  checkRoom() {
    return true;
  }

  // this function will be use for getting run-time type information
  getTypes() {
    return {
      city: City,
      roomTypes: RoomType
    };
  }
}

class RoomType implements IDeserializable {
  id: number = 0;
  name: string = "";
  roomPrices: Array<RoomPrice> = [];

  // getter example
  get totalPrice() {
    return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
  }

  getTypes() {
    return {
      roomPrices: RoomPrice
    };
  }
}

class RoomPrice {
  price: number = 0;
  date: string = "";
}

class City {
  id: number = 0;
  name: string = "";
}
// #endregion

// #region ### test code ###
var jsonObj = {
  id: 1,
  name: "hotel1",
  address: "address1",
  city: {
    id: 1,
    name: "city1"
  },
  roomTypes: [
    {
      id: 1,
      name: "single",
      roomPrices: [
        {
          price: 1000,
          date: "2020-02-20"
        },
        {
          price: 1500,
          date: "2020-02-21"
        }
      ]
    },
    {
      id: 2,
      name: "double",
      roomPrices: [
        {
          price: 2000,
          date: "2020-02-20"
        },
        {
          price: 2500,
          date: "2020-02-21"
        }
      ]
    }
  ],
  facilities: ["facility1", "facility2"]
};

var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);

console.log(hotelInstance.city.name);
console.log(hotelInstance.nameAndAddress); // getter
console.log(hotelInstance.checkRoom()); // function
console.log(hotelInstance.roomTypes[0].totalPrice); // getter
// #endregion

-1

puoi fare come di seguito

export interface Instance {
  id?:string;
  name?:string;
  type:string;
}

e

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });

Questo in realtà non produrrà un'istanza di runtime del tipo di oggetto previsto. Sembrerà funzionare quando il tuo tipo ha solo proprietà primitive, ma fallirà quando un tipo ha metodi. Le definizioni dell'interfaccia non sono inoltre disponibili in fase di esecuzione (solo tempo di compilazione).
Todd

-1
**model.ts**
export class Item {
    private key: JSON;
    constructor(jsonItem: any) {
        this.key = jsonItem;
    }
}

**service.ts**
import { Item } from '../model/items';

export class ItemService {
    items: Item;
    constructor() {
        this.items = new Item({
            'logo': 'Logo',
            'home': 'Home',
            'about': 'About',
            'contact': 'Contact',
        });
    }
    getItems(): Item {
        return this.items;
    }
}

chiama il contenuto come nell'esempio seguente:
user8390810,

<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810

Questo non sembra "istanziare] le classi secondo necessità".
LexieHankins,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.