¿Cómo inicializo un objeto TypeScript con un objeto JSON?


198

Recibo un objeto JSON de una llamada AJAX a un servidor REST. Este objeto tiene nombres de propiedad que coinciden con mi clase TypeScript (esto es una continuación de esta pregunta ).

¿Cuál es la mejor manera de inicializarlo? No creo que esto funcione porque la clase (& objeto JSON) tiene miembros que son listas de objetos y miembros que son clases, y esas clases tienen miembros que son listas y / o clases.

Pero preferiría un enfoque que busque los nombres de los miembros y los asigne, creando listas e instanciando clases según sea necesario, por lo que no tengo que escribir código explícito para cada miembro en cada clase (¡hay MUCHO!)


1
¿Por qué preguntaste esto nuevamente (como la respuesta que proporcioné en la otra pregunta decía que esto no funcionaría y que se trataba de copiar propiedades en un objeto existente)?
WiredPrairie

1
posible duplicado de Cómo
convierto

3
@WiredPrairie esta pregunta es diferente, me pregunta si puedo recorrer las propiedades una por una y asignarlas. La otra pregunta era si podía lanzarlo.
David Thielen

1
@WiredPrairie cont: si continúa sumergiéndose en las propiedades hasta llegar solo a los tipos primitivos, entonces se pueden asignar a través de ellas.
David Thielen

2
Todavía está copiando todos los valores tal como sugerí que tendría que hacer. No hay una nueva forma de hacer esto en TypeScript, ya que es un diseño fundamental de JavaScript. Para objetos grandes, es posible que no desee copiar ningún valor y simplemente "actuar" en la estructura de datos.
WiredPrairie

Respuestas:


188

Estas son algunas tomas rápidas para mostrar algunas formas diferentes. De ninguna manera son "completos" y, como descargo de responsabilidad, no creo que sea una buena idea hacerlo así. Además, el código no está demasiado limpio, ya que lo escribí todo bastante rápido.

También como una nota: por supuesto, las clases deserializables deben tener constructores predeterminados, como es el caso en todos los demás idiomas donde estoy al tanto de cualquier deserialización. Por supuesto, Javascript no se quejará si llama a un constructor no predeterminado sin argumentos, pero es mejor que la clase esté preparada para eso (además, realmente no sería la "forma de tipografía").

Opción # 1: no hay información de tiempo de ejecución

El problema con este enfoque es principalmente que el nombre de cualquier miembro debe coincidir con su clase. Lo que lo limita automáticamente a un miembro del mismo tipo por clase y rompe varias reglas de buenas prácticas. Le recomiendo encarecidamente que no lo haga, pero solo enumérelo aquí porque fue el primer "borrador" cuando escribí esta respuesta (que también explica por qué los nombres son "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);

Opción # 2: la propiedad de nombre

Para deshacernos del problema en la opción # 1, necesitamos tener algún tipo de información de qué tipo es un nodo en el objeto JSON. El problema es que en Typecript, estas cosas son construcciones en tiempo de compilación y las necesitamos en tiempo de ejecución, pero los objetos de tiempo de ejecución simplemente no tienen conocimiento de sus propiedades hasta que se establecen.

Una forma de hacerlo es haciendo que las clases conozcan sus nombres. Sin embargo, también necesita esta propiedad en JSON. En realidad, solo lo necesitas en el 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);

Opción n. ° 3: indicar explícitamente los tipos de miembros

Como se indicó anteriormente, la información de tipo de los miembros de la clase no está disponible en tiempo de ejecución, es decir, a menos que la hagamos disponible. Solo necesitamos hacer esto para los miembros no primitivos y estamos listos para comenzar:

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

Opción # 4: la forma detallada, pero ordenada

Actualización 01/03/2016: Como @GameAlchemist señaló en los comentarios ( idea , implementación ), a partir de Typecript 1.7, la solución que se describe a continuación se puede escribir de una mejor manera utilizando decoradores de clase / propiedad.

La serialización siempre es un problema y, en mi opinión, la mejor manera es la que no es la más corta. De todas las opciones, esto es lo que preferiría porque el autor de la clase tiene control total sobre el estado de los objetos deserializados. Si tuviera que adivinar, diría que todas las demás opciones, tarde o temprano, lo meterán en problemas (a menos que Javascript presente una forma nativa de lidiar con esto).

Realmente, el siguiente ejemplo no le hace justicia a la flexibilidad. Realmente solo copia la estructura de la clase. Sin embargo, la diferencia que debe tener en cuenta aquí es que la clase tiene el control total para usar cualquier tipo de JSON que quiera controlar el estado de toda la clase (puede calcular cosas, 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
La opción # 4 es lo que yo llamaría un camino razonable. Aún necesita escribir el código de deserialización, pero está en la misma clase y es totalmente controlable. Si viene de Java, esto es comparable a tener que escribir equalso toStringmétodos (solo que generalmente los genera automáticamente). No debería ser demasiado difícil escribir un generador deserializesi quisieras, pero no puede ser una automatización en tiempo de ejecución.
Ingo Bürk

2
@ IngoBürk, sé que estoy haciendo esta pregunta 2 años después, pero ¿cómo funcionará esto en una matriz de objetos? El código de muestra anterior funciona bien para el objeto JSON. ¿Cómo se puede usar para una variedad de objetos?
Pratik Gaikwad

2
Una observación lateral: desde el 1.7, (ciertamente más reciente que su respuesta), el mecanografiado proporciona decoradores de clase / propiedad que permiten escribir la cuarta solución de una manera más ordenada.
GameAlchemist

1
La mejor documentación que encontré es una respuesta de StackOverflow: stackoverflow.com/a/29837695/856501 . Utilicé decoradores en un proyecto mío, y aunque quisiera algunas otras características, debo decir que funcionan de maravilla.
GameAlchemist

2
Todavía no saltaría a los decoradores para un proyecto de producción, tenga en cuenta que todavía son una característica experimental. No basaría el código del mundo real en "experimentos" porque, en lo que a nosotros respecta, pueden desaparecer en la próxima versión y tendrías que reescribir un montón de código o quedarte atascado para siempre en una versión antigua de TS. Just my $ .02
RVP

35

puede usar Object.assignNo sé cuándo se agregó esto, actualmente estoy usando Typecript 2.0.2, y esta parece ser una característica de ES6.

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

aqui esta HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

esto es lo que Chrome dice que es

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

para que pueda ver que no hace la asignación de forma recursiva


2
Así que, básicamente, es la siguiente: Object.assign. ¿Por qué entonces tenemos dos respuestas tipo léxico sobre esta?
phil294

18
@Blauhim Porque Object.assignno funcionará de forma recursiva y no creará instancias de los tipos de objeto correctos, dejando los valores como Objectinstancias. Si bien está bien para tareas triviales, la serialización de tipo complejo no es posible con ella. Por ejemplo, si una propiedad de clase es de un tipo de clase personalizado, JSON.parse+ Object.assigninstanciará esa propiedad en Object. Los efectos secundarios incluyen métodos faltantes y accesorios.
John Weisz

@JohnWeisz, la clase de nivel superior de asignación de objetos tiene el tipo correcto, y mencioné lo recursivo en esto ... eso dijo, YMMV, y esos podrían ser un factor decisivo.
xenoterracide

Citado directamente de la pregunta: "la clase tiene miembros que son listas de objetos y miembros que son clases, y esas clases tienen miembros que son listas y / o clases. Prefiero un enfoque que busque el miembro los nombra y los asigna, creando listas y creando instancias de clases según sea necesario, por lo que no tengo que escribir código explícito para cada miembro en cada clase " , que no es el caso Object.assign, donde todavía se trata de escribir instancias anidadas por mano. Este enfoque está bien para objetos de nivel tutorial muy simples, pero no para uso real.
John Weisz

@JohnWeisz seguro, en su mayoría respondió con esto porque no estaba en ninguna respuesta y parecía simple para algunos casos de uso. Estoy seguro de que también podría usarse en combinación con otras respuestas, como la reflexión, para hacer lo que está buscando. También lo escribí en parte para recordarlo más tarde. Al mirar estas respuestas y haber usado y escrito bibliotecas mucho más potentes, no parece haber nada disponible para "uso real".
xenoterracide

34

TLDR: TypedJSON (prueba de concepto de trabajo)


La raíz de la complejidad de este problema es que necesitamos deserializar JSON en tiempo de ejecución utilizando información de tipo que solo existe en tiempo de compilación . Esto requiere que la información de tipo esté disponible de alguna manera en tiempo de ejecución.

Afortunadamente, esto se puede resolver de una manera muy elegante y robusta con decoradores y ReflectDecorators :

  1. Use decoradores de propiedades en propiedades sujetas a serialización, para registrar información de metadatos y almacenar esa información en algún lugar, por ejemplo en el prototipo de clase
  2. Alimente esta información de metadatos a un inicializador recursivo (deserializador)

 

Información de tipo de grabación

Con una combinación de ReflectDecorators y decoradores de propiedades, la información de tipo puede registrarse fácilmente sobre una propiedad. Una implementación rudimentaria de este enfoque sería:

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 cualquier propiedad dada, el fragmento anterior agregará una referencia de la función constructora de la propiedad a la __propertyTypes__propiedad oculta en el prototipo de clase. Por ejemplo:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

Y eso es todo, tenemos la información de tipo requerida en tiempo de ejecución, que ahora se puede procesar.

 

Información de tipo de procesamiento

Primero necesitamos obtener una Objectinstancia usando JSON.parse- después de eso, podemos iterar sobre los entrantes en __propertyTypes__(recopilados anteriormente) e instanciar las propiedades requeridas en consecuencia. Se debe especificar el tipo del objeto raíz, de modo que el deserializador tenga un punto de partida.

Una vez más, una implementación muy simple de este enfoque sería:

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

La idea anterior tiene una gran ventaja de deserializar por tipos esperados (para valores complejos / de objeto), en lugar de lo que está presente en el JSON. Si Personse espera a, entonces se Personcrea una instancia. Con algunas medidas de seguridad adicionales para tipos y matrices primitivos, este enfoque se puede hacer seguro, que resista cualquier JSON malicioso.

 

Casos de borde

Sin embargo, si ahora está contento de que la solución sea así de simple, tengo algunas malas noticias: hay una gran cantidad de casos extremos que deben ser atendidos. Solo algunos de los cuales son:

  • Matrices y elementos de matriz (especialmente en matrices anidadas)
  • Polimorfismo
  • Clases abstractas e interfaces
  • ...

Si no quiere jugar con todo esto (apuesto a que no), me gustaría recomendar una versión experimental funcional de una prueba de concepto utilizando este enfoque, TypedJSON , que creé para abordar este problema exacto, un problema que me enfrento a diario.

Debido a que los decoradores todavía se consideran experimentales, no recomendaría usarlo para producción, pero hasta ahora me sirvió mucho.


TypedJSON funcionó muy bien; Muchas gracias por la referencia.
Neil

Buen trabajo, has encontrado una solución muy elegante para un problema que me ha estado preocupando por un tiempo. ¡Seguiré tu proyecto muy de cerca!
John Strickler

12

He estado usando a este tipo para hacer el trabajo: https://github.com/weichx/cerialize

Es muy simple pero poderoso. Soporta:

  • Serialización y deserialización de un árbol completo de objetos.
  • Propiedades persistentes y transitorias en el mismo objeto.
  • Ganchos para personalizar la lógica de (des) serialización.
  • Puede (des) serializar en una instancia existente (ideal para Angular) o generar nuevas instancias.
  • etc.

Ejemplo:

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

He creado una herramienta que genera interfaces TypeScript y un "mapa de tipos" en tiempo de ejecución para realizar la verificación de tipos en tiempo de ejecución con los resultados de JSON.parse: ts.quicktype.io

Por ejemplo, dado este JSON:

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

quicktype produce la siguiente interfaz TypeScript y el mapa de tipos:

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

Luego verificamos el resultado de JSON.parsecontra el tipo de mapa:

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

He omitido algo de código, pero puedes probar quicktype para los detalles.


1
Después de investigar muchas horas y probar algunas técnicas de análisis, puedo decir que es una solución excelente, principalmente porque los decoradores aún son experimentales. * El enlace original está roto para mí; pero ts.quicktype.io funciona. * Convertir el esquema JSON a JSON es un buen primer paso.
LexieHankins

3

Opción # 5: Uso de constructores de mecanografiado y jQuery.extend

Este parece ser el método más fácil de mantener: agregue un constructor que tome como parámetro la estructura json y extienda el objeto json. De esa manera, puede analizar una estructura json en todo el modelo de aplicación.

No es necesario crear interfaces o enumerar propiedades en el constructor.

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

En su devolución de llamada ajax donde recibe una empresa para calcular los salarios:

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

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

de donde $.extendviene
whale_steward

@whale_steward Supongo que el autor se refiere a la biblioteca jQuery. En el mundo de JavaScript, '$' es a menudo alguien que usa jQuery.
Nick Roth

¿Cómo importarlo? solo incluirlo en la cabeza html es suficiente?
whale_steward

Sí, actualizo la respuesta para reemplazar $ por jQuery. importe jQuery.js en el encabezado html, e instale y agregue @ types / jquery en su paquete.json, sección devDependencies.
Anthony Brenelière

1
Tenga en cuenta que en Javascript, debe hacerlo Object.assign, lo que elimina esta dependencia de jQuery.
Léon Pelletier

2

Para objetos simples, me gusta este 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'});

Aprovechar la capacidad de definir propiedades en el constructor le permite ser conciso.

Esto le proporciona un objeto escrito (frente a todas las respuestas que usan Object.assign o alguna variante, que le proporciona un Objeto) y no requiere bibliotecas o decoradores externos.


1

La cuarta opción descrita anteriormente es una manera simple y agradable de hacerlo, que debe combinarse con la segunda opción en el caso de que tenga que manejar una jerarquía de clases como, por ejemplo, una lista de miembros que es una de las subclases de una superclase de miembro, por ejemplo, el director extiende al miembro o el estudiante extiende al miembro. En ese caso, debe dar el tipo de subclase en formato json


1

JQuery .extend hace esto por usted:

var mytsobject = new mytsobject();

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

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

1

Lo mejor que encontré para este propósito es el transformador de clase. github.com/typestack/class-transformer

Así es como lo usas:

Alguna clase:

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

Si usa el decorador @Type, también se crearán propiedades anidadas.


0

Quizás no sea real, sino una solución simple:

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

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

¡trabaje para dependencias difíciles también!


9
Este enfoque no funciona realmente como se esperaba. Si inspecciona los resultados de tiempo de ejecución, bazserá de tipo Objecty no de tipo. Bar.Funciona en este caso simple porque Barno tiene métodos (solo propiedades primitivas). Si Bartuviera un método como isEnabled(), este enfoque fallaría ya que ese método no estaría en la cadena JSON serializada.
Todd

0

Otra opción 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

usar así

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. mantiene tus clases simples
  2. inyección disponible para las fábricas para mayor flexibilidad

0

Personalmente prefiero la opción # 3 de @Ingo Bürk. Y mejoré sus códigos para admitir una matriz de datos complejos y una matriz de datos 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

puedes hacer lo siguiente

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

y

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

En realidad, esto no producirá una instancia de tiempo de ejecución de su tipo de objeto esperado. Parecerá funcionar cuando su tipo solo tenga propiedades primitivas, pero fallará cuando un tipo tenga métodos. Las definiciones de interfaz tampoco están disponibles en tiempo de ejecución (solo tiempo de compilación).
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;
    }
}

llame al contenido como el siguiente ejemplo:
user8390810

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

Esto no parece "[instanciar] clases según sea necesario".
LexieHankins
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.