Como inicializar um objeto TypeScript com um objeto JSON


198

Eu recebo um objeto JSON de uma chamada AJAX para um servidor REST. Este objeto possui nomes de propriedades que correspondem à minha classe TypeScript (este é o seguimento desta pergunta ).

Qual é a melhor maneira de inicializá-lo? Eu não acho que isso funcionará porque a classe (objeto & JSON) possui membros que são listas de objetos e membros que são classes, e essas classes têm membros que são listas e / ou classes.

Mas eu prefiro uma abordagem que procure os nomes dos membros e os atribua, criando listas e instanciando classes conforme necessário, para que eu não precise escrever código explícito para cada membro de cada classe (há MUITO!)


1
Por que você perguntou novamente (como a resposta que eu forneci na outra pergunta disse que isso não iria funcionar e que se tratava de copiar propriedades em um objeto existente)?
precisa saber é o seguinte


3
@WiredPrairie esta pergunta é diferente, está perguntando se eu posso percorrer as propriedades uma por uma e atribuí-las. As outras perguntas estavam perguntando se eu poderia lançá-lo.
David Thielen

1
@WiredPrairie cont: Se você continuar mergulhando nas propriedades até chegar apenas aos tipos primitivos, eles poderão ser atribuídos.
David Thielen 06/04

2
Ainda está copiando todos os valores, como sugeri que você precisaria fazer. Não há nova maneira de fazer isso no TypeScript, pois é um design fundamental do JavaScript. Para objetos grandes, você pode não querer copiar nenhum valor e apenas "agir" na estrutura de dados.
WiredPrairie

Respostas:


188

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

12
A opção 4 é o que eu chamaria de caminho razoável a seguir. Você ainda precisa escrever o código de desserialização, mas ele está na mesma classe e é totalmente controlável. Se você é oriundo de Java, isso é comparável a ter que escrever equalsou toStringmétodos (apenas que você normalmente os gera automaticamente). Não deve ser muito difícil escrever um gerador, deserializese você quiser, mas não pode ser a automação em tempo de execução.
Ingo Bürk

2
@ IngoBürk, eu sei que estou fazendo essa pergunta 2 anos depois, mas como isso funcionará na matriz de objetos? O código de exemplo acima funciona bem para o objeto JSON. como pode ser usado para matriz de objetos?
Pratik Gaikwad

2
Uma observação lateral: desde o 1.7, (reconhecidamente mais recente que a sua resposta), o texto datilografado fornece decoradores de classe / propriedade que permitem escrever a quarta solução de maneira mais organizada.
GameAlchemist

1
A melhor documentação que encontrei é uma resposta do StackOverflow: stackoverflow.com/a/29837695/856501 . Eu usei decoradores em um projeto meu e, embora eu queira alguns outros recursos, devo dizer que eles funcionam como um encanto.
GameAlchemist

2
Ainda não apostaria nos decoradores de um projeto de produção - tenha em mente que eles ainda são um recurso experimental. Eu não basearia o código do mundo real em "experimentos" porque, no que diz respeito a nós, eles podem ter desaparecido na próxima versão e você teria que reescrever um monte de código ou ficar preso para sempre em uma versão antiga do TS. Apenas meu $ .02
RVP

35

você pode usar Object.assignNão sei quando isso foi adicionado. No momento, estou usando o Typescript 2.0.2 e esse parece ser um recurso do ES6.

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

aqui está HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

aqui está o que o Chrome diz que é

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

para que você possa ver que não faz a atribuição recursivamente


2
Então, basicamente, é a seguinte: Object.assign. Por que temos duas respostas semelhantes ao léxico acima desta?
phil294

18
@Blauhim Porque Object.assignnão funcionará recursivamente e não instanciar tipos de objetos corretos, deixando valores como Objectinstâncias. Embora seja adequado para tarefas triviais, a serialização de tipo complexo não é possível com ele. Por exemplo, se uma propriedade de classe for de um tipo de classe customizada, JSON.parse+ Object.assigninstanciará essa propriedade para Object. Os efeitos colaterais incluem métodos e acessadores ausentes.
John Weisz

@JohnWeisz, a classe de nível superior de atribuição de objetos tem o tipo correto, e eu mencionei a coisa recursiva nisto ... que dizia, YMMV, e esses podem ser disjuntores.
Xenoterracide

Citado diretamente na pergunta: "a classe tem membros que são listas de objetos e membros que são classes, e essas classes têm membros que são listas e / ou classes [...] eu preferiria uma abordagem que procure o membro nomeia e os atribui, criando listas e instanciando classes conforme necessário, para que eu não precise escrever código explícito para todos os membros de todas as classes " - o que não é o caso Object.assign, onde ainda se trata de escrever instanciações aninhadas por mão. Essa abordagem é adequada para objetos muito simples no nível de tutorial, mas não para uso real.
John Weisz

@JohnWeisz claro, respondeu principalmente com isso porque não estava em nenhuma resposta e parecia simples para alguns casos de uso. Estou certo de que também poderia ser usado em combinação com outras respostas, como reflexão, para fazer o que você está procurando. Também escrevi em parte para lembrar mais tarde. Olhando para essas respostas e tendo usado e escrito bibliotecas muito mais poderosas, parece não haver nada disponível para "uso real".
Xenoterracide

34

TLDR: TypedJSON (prova de conceito de trabalho)


A raiz da complexidade desse problema é que precisamos desserializar o JSON em tempo de execução usando informações de tipo que só existem em tempo de compilação . Isso requer que as informações de tipo sejam disponibilizadas de alguma forma no tempo de execução.

Felizmente, isso pode ser resolvido de uma maneira muito elegante e robusta com decoradores e ReflectDecorators :

  1. Use decoradores de propriedades em propriedades sujeitas a serialização, para registrar informações de metadados e armazenar essas informações em algum lugar, por exemplo, no protótipo de classe
  2. Alimente essas informações de metadados a um inicializador recursivo (desserializador)

 

Informações sobre o tipo de gravação

Com uma combinação de ReflectDecorators e decoradores de propriedades, as informações de tipo podem ser facilmente registradas sobre uma propriedade. Uma implementação rudimentar dessa abordagem seria:

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

Para qualquer propriedade, o trecho acima adicionará uma referência da função construtora da propriedade à __propertyTypes__propriedade oculta no protótipo de classe. Por exemplo:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

E é isso, temos as informações de tipo necessárias em tempo de execução, que agora podem ser processadas.

 

Informações de tipo de processamento

Primeiro precisamos obter uma Objectinstância usando JSON.parse- depois disso, podemos iterar sobre as entradas em __propertyTypes__(coletadas acima) e instanciar as propriedades necessárias de acordo. O tipo do objeto raiz deve ser especificado, para que o desserializador tenha um ponto de partida.

Novamente, uma implementação simples e simples dessa abordagem seria:

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

A idéia acima tem uma grande vantagem de desserializar por tipos esperados (para valores complexos / de objetos), em vez do que está presente no JSON. Se a Personé esperado, é uma Personinstância criada. Com algumas medidas de segurança adicionais em vigor para tipos e matrizes primitivos, essa abordagem pode ser protegida, resistindo a qualquer JSON malicioso.

 

Casos Edge

No entanto, se você está feliz agora que a solução é que simples, eu tenho uma má notícia: há um grande número de casos de ponta que precisam ser tomadas de cuidados. Apenas alguns dos quais são:

  • Matrizes e elementos da matriz (especialmente em matrizes aninhadas)
  • Polimorfismo
  • Classes abstratas e interfaces
  • ...

Se você não quiser mexer com tudo isso (aposto que não), ficaria feliz em recomendar uma versão experimental de trabalho de uma prova de conceito utilizando essa abordagem, o TypedJSON - que eu criei para resolver esse problema exato, um problema que eu enfrento diariamente.

Devido à forma como os decoradores ainda estão sendo considerados experimentais, eu não recomendaria usá-lo para uso em produção, mas até agora isso me serviu bem.


TypedJSON funcionou muito bem; muito obrigado pela referência.
Neil

Bom trabalho, você encontrou uma solução muito elegante para um problema que me incomoda há um tempo. Vou acompanhar seu projeto de muito perto!
precisa saber é o seguinte

12

Eu tenho usado esse cara para fazer o trabalho: https://github.com/weichx/cerialize

É muito simples, mas poderoso. Suporta:

  • Serialização e desserialização de toda uma árvore de objetos.
  • Propriedades persistentes e transitórias no mesmo objeto.
  • Ganchos para personalizar a lógica de (des) serialização.
  • Ele pode (des) serializar em uma instância existente (ótima para Angular) ou gerar novas instâncias.
  • etc.

Exemplo:

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

Eu criei uma ferramenta que gera interfaces TypeScript e um "mapa de tipos" em tempo de execução para executar a digitação em tempo de execução com os resultados de JSON.parse: ts.quicktype.io

Por exemplo, dado esse JSON:

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

quicktype produz a seguinte interface TypeScript e tipo de mapa:

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",
    },
};

Em seguida, verificamos o resultado de JSON.parsecontra o mapa de tipos:

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

Deixei de fora algum código, mas você pode tentar o quicktype para obter os detalhes.


1
Depois de fazer muitas horas de pesquisa e experimentar algumas técnicas de análise, posso dizer que essa é uma excelente solução - principalmente porque os decoradores ainda são experimentais. * O link original está quebrado para mim; mas ts.quicktype.io funciona. * Converter o esquema JSON em JSON é um bom primeiro passo.
LexieHankins

3

Opção 5: Usando construtores TypeScript e jQuery.extend

Este parece ser o método mais sustentável: adicione um construtor que tome como parâmetro a estrutura json e estenda o objeto json. Dessa forma, você pode analisar uma estrutura json em todo o modelo de aplicativo.

Não há necessidade de criar interfaces ou listar propriedades no construtor.

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

No retorno de chamada ajax, você recebe uma empresa para calcular salários:

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

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

de onde $.extendvem?
Whale_steward

@whale_steward Eu diria que o autor está se referindo à biblioteca jQuery. No mundo JavaScript, '$' é frequentemente alguém que usa jQuery.
Nick Roth

como importá-lo? basta incluí-lo na cabeça html é suficiente?
whale_steward

sim, atualizo a resposta para substituir $ por jQuery. importe jQuery.js no cabeçalho html e instale e adicione @ types / jquery na seção package.json, devDependencies.
Anthony Brenelière

1
Observe que, em Javascript, você deve fazer isso Object.assign, o que remove essa dependência do jQuery.
Léon Pelletier

2

Para objetos simples, eu gosto deste método:

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

Aproveitar a capacidade de definir propriedades no construtor permite que seja conciso.

Isso gera um objeto digitado (em comparação com todas as respostas que usam Object.assign ou alguma variante, que fornece um objeto) e não requer bibliotecas ou decoradores externos.


1

A quarta opção descrita acima é uma maneira simples e agradável de fazer isso, que deve ser combinada com a segunda opção no caso em que você precisa lidar com uma hierarquia de classes como, por exemplo, uma lista de membros que é uma das ocorrências de subclasses de uma superclasse de membro, por exemplo, o diretor estende o membro ou o aluno estende o membro. Nesse caso, você deve fornecer o tipo de subclasse no formato json


1

O JQuery .extend faz isso para você:

var mytsobject = new mytsobject();

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

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

0

Talvez não seja uma solução real, mas simples:

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

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

trabalhar para dependências difíceis também !!!


9
Essa abordagem não funciona como esperado. Se você inspecionar os resultados do tempo de execução, bazserá do tipo Objecte não do tipo Bar.. Funciona neste caso simples porque Barnão possui métodos (apenas propriedades primitivas). Se Bartivesse um método parecido isEnabled(), essa abordagem falharia, pois esse método não estaria na sequência JSON serializada.
Todd

0

Outra opção usando fábricas

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

use assim

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. mantém suas aulas simples
  2. injeção disponível nas fábricas para flexibilidade

0

o melhor que encontrei para esse fim é o transformador de classe. github.com/typestack/class-transformer

É assim que você o usa:

Alguma classe:

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 você usar as propriedades aninhadas do decorador @Type, também serão criadas.


0

Pessoalmente, prefiro a opção nº 3 do @Ingo Bürk. E aprimorei seus códigos para suportar uma matriz de dados complexos e uma matriz de dados primitivos.

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

você pode fazer como abaixo

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

e

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

Na verdade, isso não produzirá uma instância de tempo de execução do seu tipo de objeto esperado. Aparecerá funcionar quando seu tipo tiver apenas propriedades primitivas, mas falhará quando um tipo tiver métodos. As definições de interface também não estão disponíveis no tempo de execução (apenas tempo de construção).
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;
    }
}

chame o conteúdo como no exemplo abaixo:
user8390810

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

Isso não parece "[instanciar] classes conforme necessário".
LexieHankins
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.