from - typescript json const




Como inicializo um objeto TypeScript com um objeto JSON (10)

Eu recebo um objeto JSON de uma chamada AJAX para um servidor REST. Este objeto tem nomes de propriedades que correspondem à minha classe TypeScript (este é um follow-on para esta questão ).

Qual é a melhor maneira de inicializá-lo? Eu não acho que this funcionará porque a classe (objeto JSON) tem membros que são listas de objetos e membros que são classes, e essas classes possuem membros que são listas e / ou classes.

Mas 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 tenha que escrever código explícito para cada membro em todas as classes (há MUITO!)


Opção # 5: usando construtores de datilografia 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;
    }
}

Em seu callback de ajax, onde você recebe uma empresa para calcular os salários:

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

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

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


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

Por exemplo, dado este JSON:

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

O quicktype produz a seguinte interface TypeScript e o 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.parse no mapa de tipos:

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

Eu deixei de fora algum código, mas você pode tentar quicktype para os detalhes.


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 uma árvore inteira de objetos.
  • Propriedades persistentes e transitórias no mesmo objeto.
  • Ganchos para personalizar a (de) lógica de serialização.
  • Pode (de) serializar em uma instância existente (excelente 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);

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

usar 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 para as fábricas para flexibilidade

Para objetos simples, 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.

Isto obtém um objeto tipado (vs todas as respostas que usam Object.assign ou alguma variante, que lhe dão um objeto) e não requer bibliotecas externas ou decoradores.


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

É assim que você 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 o @Type decorator, as propriedades aninhadas também serão criadas.


você pode fazer como abaixo

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

e

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

TLDR: TypedJSON (prova de conceito em funcionamento)

A raiz da complexidade desse problema é que precisamos desserializar 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 decorators e ReflectDecorators :

  1. Use decoradores de propriedade em propriedades que estão 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 para um inicializador recursivo (desserializador)

Gravando informações de tipo

Com uma combinação de ReflectDecorators e decoradores de propriedade, 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 snippet acima adicionará uma referência da função construtora da propriedade à propriedade __propertyTypes__ 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.

Processamento de informações de tipo

Primeiro precisamos obter uma instância de Object 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 inicial.

Mais uma vez, uma implementação 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 ideia acima tem uma grande vantagem de desserializar por tipos esperados (para valores complexos / objetos), em vez do que está presente no JSON. Se uma Person for esperada, então é uma instância Person criada. Com algumas medidas de segurança adicionais em vigor para tipos e matrizes primitivos, essa abordagem pode ser protegida, resistindo a qualquer JSON mal-intencionado.

Casos de Borda

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

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

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

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


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




typescript