Como inicializo um objecto de dactilografia com um objecto JSON

Recebo um objecto JSON de uma chamada AJAX para um servidor de descanso. Este objecto tem nomes de propriedades que correspondem à minha classe de dactilografia (esta é uma sequência para esta questão).

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

Mas prefiro uma abordagem que olhe para cima nomes de membros e atribui-los através, criando listas e instanciando classes conforme necessário, para que eu não tenha que escrever código explícito para cada membro em cada classe (há muito!)

Author: wonea, 2014-04-05

12 answers

Estes são alguns tiros rápidos para mostrar algumas maneiras diferentes. Eles não são de forma alguma "completos" e como um aviso, Eu não acho que seja uma boa idéia fazê-lo assim. Além disso, o código não é muito limpo, uma vez que eu digitei-o juntos muito rapidamente.

Também como nota: é claro que as classes deserializáveis precisam ter construtores padrão como é o caso em todas as outras línguas onde eu estou ciente de deserialização de qualquer tipo. Claro, Javascript não vai reclamar se você ligar para um constructor não-padrão sem argumentos, mas a classe melhor ser preparado para ele então (além disso, ele realmente não seria o "typescripty way").

Opção #1: nenhuma informação em tempo de execução

O problema com esta abordagem é que o nome de qualquer membro deve corresponder à sua classe. O que automaticamente o limita a um membro do mesmo tipo por classe e quebra várias regras de boas práticas. Eu aconselho fortemente contra isso, mas apenas listá-lo aqui porque foi o primeiro "rascunho" quando eu escrevi esta resposta (que é 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: O nome propriedade

Para nos livrarmos do problema na opção #1, precisamos de ter algum tipo de Informação do tipo a nodo no objecto JSON. O problema é que em Typescript, essas coisas são construções de tempo de compilação e nós precisamos delas em tempo de execução-mas objetos de tempo de execução simplesmente não têm consciência de suas propriedades até que eles sejam definidos.

Uma maneira de o fazer é fazendo classes conscientes dos seus nomes. Mas também precisas desta propriedade no JSON. Na verdade, só precisas 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 # 3: indicar explicitamente os tipos de membros

Como indicado acima, a informação tipo dos membros da classe não está disponível em tempo de execução – isto é, a menos que a disponibilizemos. Nós só precisamos fazer isso para os membros não primitivos e estamos prontos para ir: [[6]}

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: o modo descritivo, mas limpo

actualizar 01/03/2016: como @GameAlchemist salientou nos comentários, a partir do Typescript 1.7, a solução descrita abaixo pode ser escrita de uma forma melhor usando decoradores de classe/propriedade.

A serialização é sempre um problema e, na minha opinião, o melhor caminho é um caminho que não é o mais curto. De todas as opções, isto é o que eu prefiro porque o autor da classe tem total controle sobre o estado dos objetos deserializados. Se tivesse de adivinhar, diria que todas as outras opções, mais cedo ou mais tarde, mais tarde, você vai ficar em apuros (a menos que Javascript vem com uma maneira nativa para lidar com isso). Na verdade, o seguinte exemplo não faz justiça à flexibilidade. Apenas copia a estrutura da turma. A diferença que você tem que ter em mente aqui, entretanto, é que a classe tem controle total para usar qualquer tipo de JSON que quer controlar o estado de toda a classe (você poderia calcular 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);
 157
Author: Ingo Bürk, 2016-03-01 22:43:49

TLDR: TypedJSON (prova do conceito de trabalho)


A raiz da complexidade deste problema é que precisamos deserializar o JSON em tempo de execução usando Informação do tipo que só existe em tempo de compilação . Isto requer que a informação tipo seja de alguma forma disponibilizada em tempo de execução.

Felizmente, isto pode ser resolvido de uma forma muito elegante e robusta com decoradores {[[25]}e reflectores:
  1. utilizar decoradores de propriedades em Propriedades sujeitas a serialização, para gravar informação de metadados e guardar essa informação algures, por exemplo no protótipo da classe
  2. dê esta informação de meta-dados a um inicializador recursivo (deserializador)

 

Registar A Informação Do Tipo

Com uma combinação de reflectores e decoradores de propriedades, a informação do tipo pode ser facilmente registrado sobre uma propriedade. Uma implementação rudimentar desta 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 uma dada propriedade, o trecho acima irá adicionar uma referência da função do construtor da propriedade à propriedade escondida {[[4]} no protótipo da 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 a informação necessária no tempo de execução, que agora pode ser processada.

 

Tipo De Processamento Informação

Primeiro precisamos de obter um Object instance using JSON.parse -- after that, we can iterate over the entires in __propertyTypes__ (collected above) and instantiate the required properties accordingly. O tipo do objeto raiz deve ser especificado, de modo que o desertor tenha um ponto de partida. Uma vez mais, uma aplicação muito simples desta 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 deseralizar por os tipos esperados (para valores complexos / objectos), em vez do que está presente na JSON. Se um Person é esperado, então é uma instância Person que é criada. Com algumas medidas de segurança adicionais em vigor para tipos primitivos e arrays, esta abordagem pode ser feita segura, que resiste a qualquer JSON malicioso.

 

Casos Extremos

No entanto, se você está feliz por a solução ser simples, tenho más notícias: há um vasto número de casos extremos que precisam ser tratados. Apenas alguns dos quais são:
  • matrizes e elementos de matriz (especialmente em matrizes aninhadas)
  • polimorfismo
  • classes e interfaces Abstractas
  • ...

Se você não quer mexer com todos estes (eu aposto que você não), eu iria ser feliz para recomendar um trabalho experimental versão de uma prova de conceito utilizando esta abordagem, TypedJSON -- que eu criado para resolver este problema exato, um problema que eu enfrento-me diariamente.

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

 29
Author: John Weisz, 2017-01-02 13:09:46

Você pode usar Object.assign Eu não sei quando isso foi adicionado, eu estou usando atualmente Typescript 2.0.2, e este parece ser um recurso 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;
}
Eis o que chrome diz que é
HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

Então você pode ver que ele não faz a atribuição recursivamente {[[7]}

 25
Author: xenoterracide, 2017-01-05 02:54:22
Tenho usado este tipo para fazer o trabalho. https://github.com/weichx/cerialize É muito simples, mas poderoso. Ele suporta:
  • serialização e deserialização de toda uma árvore de objectos.
  • Propriedades persistentes e transitórias no mesmo objecto.
  • Ganchos para personalizar a lógica de serialização (de).
  • Ele pode (de)ser serializado em uma instância existente (grande para Angular) ou gerar novo instancia.
  • 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);
 10
Author: André, 2017-02-18 13:07:27

Opção # 5: utilização de construtores de dactilografia e de artigos jQuery.Extender

Este parece ser o método mais possível: adicionar um construtor que toma como parâmetro a estrutura json, e extender o objecto json. Dessa forma você pode processar uma estrutura json em todo o modelo de Aplicação.

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 seu callback ajax onde 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()
}
 2
Author: Anthony Brenelière, 2017-02-06 08:47:11

A 4ª opção descrita acima é uma forma simples e agradável de o fazer, que tem de ser combinada com a 2.ª opção no caso de ter de lidar com uma hierarquia de classes como, por exemplo, uma lista de membros que é qualquer uma das ocorrências de subclasses de uma super classe membro, por exemplo, o Director extende Membro ou o estudante extende Membro. Nesse caso, terá de indicar o tipo de subclasse no formato json

 1
Author: Xavier Méhaut, 2015-12-02 03:29:52
Criei uma ferramenta que gera interfaces TypeScript e um "mapa tipo" em tempo de execução para comparar os resultados de JSON.parse: ts.quicktype.io

Por exemplo, dado este JSON:

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

Quicktype produz a seguinte interface de tipo e o mapa do tipo:

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

Depois verificamos o resultado de JSON.parse com o tipo de mapa:

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

Deixei de fora algum código, mas você pode tentar quicktype {[8] } para os detalhes.

 1
Author: David Siegel, 2017-09-09 01:14:11

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

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

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

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

 0
Author: Михайло Пилип, 2016-07-28 09:30:14
JQuery .o extend Faz isto por ti:
var mytsobject = new mytsobject();

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

$.extend(mytsobject, newObj); //mytsobject will now contain a & b
 0
Author: Daniel, 2016-08-12 14:55:10

Outra opção utilizando 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 as aulas simples
  2. injecção à disposição das fábricas para flexibilidade
 0
Author: Anthony Johnston, 2017-09-13 21:50:50

Você pode fazer como abaixo

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

E

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
 -1
Author: Md Ayub Ali Sarker, 2016-10-19 16:10:04
**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;
    }
}
 -1
Author: user8390810, 2017-12-27 04:15:43