pass - Como faço para converter um objeto JSON para uma classe typescript




typescript pass json object as parameter (12)

Eu li um objeto JSON de um servidor REST remoto. Esse objeto JSON possui todas as propriedades de uma classe de datilografia (por design). Como faço para transmitir esse objeto JSON recebido para um tipo var?

Eu não quero preencher um typescript var (ou seja, ter um construtor que leva esse objeto JSON). É grande e copiar todo o subobjeto por subobjeto e propriedade por propriedade levaria muito tempo.

Update: Você pode, no entanto, convertê-lo em uma interface typescript!


Ainda não há nada para verificar automaticamente se o objeto JSON recebido do servidor tem as propriedades de interface esperadas (leitura está de acordo com as) do typescript. Mas você pode usar guardas de tipo definidos pelo usuário

Considerando a seguinte interface e um objeto json bobo (poderia ter sido qualquer tipo):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

Três maneiras possíveis:

A. Asserção de tipo ou elenco simples estático colocado após a variável

const myObject: MyInterface = json as MyInterface;

B. Elenco estático simples, antes da variável e entre diamantes

const myObject: MyInterface = <MyInterface>json;

C. Elenco dinâmico avançado, você verifica a estrutura do objeto

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

Você pode brincar com este exemplo aqui

Note que a dificuldade aqui é escrever a função isMyInterface . Espero que o TS adicione um decorador mais cedo ou mais tarde para exportar a digitação complexa para o tempo de execução e deixe que o tempo de execução verifique a estrutura do objeto quando necessário. Por enquanto, você poderia usar um validador de esquema json cujo propósito é aproximadamente o mesmo OU esse gerador de função de verificação de tipo de tempo de execução


Assumindo que o json possua as mesmas propriedades da sua classe de datilografia, você não precisa copiar suas propriedades do Json para o seu objeto datilografado. Você apenas terá que construir seu objeto Typescript passando os dados json no construtor.

Em seu retorno de chamada ajax, você recebe uma empresa:

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

   // call the methods on your newCompany object ...
}

Em para fazer esse trabalho:

1) Adicione um construtor em sua classe Typescript que use os dados do json como parâmetro. Nesse construtor você estende seu objeto json com jQuery, assim: $.extend( this, jsonData) . $ .extend permite manter os protótipos do javascript enquanto adiciona as propriedades do objeto json.

2) Observe que você terá que fazer o mesmo para objetos vinculados. No caso de Funcionários no exemplo, você também cria um construtor, levando a parte dos dados do json para os funcionários. Você chama $ .map para traduzir os funcionários do json para objetos de Empregados datilografados.

export class Company
{
    Employees : Employee[];

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

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

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

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

Essa é a melhor solução que encontrei ao lidar com classes e objetos json.


Esta é uma opção simples e muito boa

let person = "{"name":"Sam","Age":"30"}";

const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);

E então você terá

objectConverted.name

Eu criei uma ferramenta simples para fazer isso beshanoe.github.io/json2ts Ao contrário json2ts.com funciona diretamente no navegador e não envia seus dados para servidores desconhecidos. Também tem várias configurações. Eu vou trabalhar para melhorar sua funcionalidade


Eu tive o mesmo problema e encontrei uma biblioteca que faz o trabalho: https://github.com/pleerock/class-transformer .

Funciona assim:

        let jsonObject = response.json() as Object;
        let fooInstance = plainToClass(Models.Foo, jsonObject);
        return fooInstance;

Ele suporta childs aninhados, mas você tem que decorar o membro da sua turma.


Eu usei essa biblioteca aqui: https://github.com/pleerock/class-transformer

<script lang="ts">
    import { plainToClass } from 'class-transformer';
</script>

Implementação:

private async getClassTypeValue() {
  const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}

Às vezes você terá que analisar os valores JSON para plainToClass para entender que são dados formatados em JSON


No TypeScript, você pode fazer uma declaração de tipo usando uma interface e genéricos da seguinte forma:

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

Onde o ILocationMap descreve a forma dos seus dados. A vantagem desse método é que seu JSON pode conter mais propriedades, mas a forma satisfaz as condições da interface.

Espero que ajude!


No meu caso isso funciona. Eu usei funções Object.assign (target, sources ...) . Primeiro, a criação do objeto correto, copia os dados do objeto json para o destino.Exemplo:

let u:User = new User();
Object.assign(u , jsonUsers);

E um exemplo mais avançado de uso. Um exemplo usando a matriz.

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}

Se você estiver usando ES6, tente isto:

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {

  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

Mas desta forma não vai funcionar no objeto ninho, infelizmente.


Uma coisa que fizemos porque somos uma loja .NET é criar essa ferramenta ( https://github.com/Centeva/TypeScripter ).

Ele constrói classes datilografadas de nossos dlls. Tudo o que temos a fazer é canalizar nossa resposta json para a classe como um param. Isso funciona bem para nós.


Você não pode simplesmente converter um resultado simples e JavaScript de uma solicitação Ajax em uma instância de classe JavaScript / TypeScript prototípica. Existem várias técnicas para fazer isso e geralmente envolvem a cópia de dados. A menos que você crie uma instância da classe, ela não terá métodos ou propriedades. Permanecerá um simples objeto JavaScript.

Enquanto se você estivesse lidando apenas com dados, você poderia simplesmente fazer uma conversão para uma interface (como é puramente uma estrutura de tempo de compilação), isso exigiria o uso de uma classe TypeScript que usa a instância de dados e executa operações com esses dados.

Alguns exemplos de copiar os dados:

  1. Copiando o Objeto AJAX JSON no Objeto Existente
  2. Analisar a sequência JSON em um protótipo de objeto específico em JavaScript

Em essência, você apenas:

var d = new MyRichObject();
d.copyInto(jsonResult);

TLDR: um forro

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

A resposta detalhada

Eu não recomendaria a abordagem Object.assign, uma vez que ela pode, inapropriadamente, distribuir sua instância de classe com propriedades irrelevantes (assim como fechamentos definidos) que não foram declaradas dentro da própria classe.

Na classe em que você está tentando desserializar, asseguro que todas as propriedades que você deseja desserializadas sejam definidas (nulo, matriz vazia, etc). Ao definir suas propriedades com valores iniciais, você expõe sua visibilidade ao tentar iterar os membros da classe para atribuir valores (veja o método de desserialização abaixo).

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

No exemplo acima, eu simplesmente criei um método de desserialização. Em um exemplo do mundo real, eu o teria centralizado em uma classe base ou método de serviço reutilizável.

Aqui está como utilizar isso em algo como um http resp ...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

Se tslint / ide reclamar que o tipo de argumento é incompatível, apenas converta o argumento no mesmo tipo usando colchetes angulares <YourClassName> , exemplo:

const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

Se você tem membros de classe que são de um tipo específico (também conhecido como: instance de outra classe), então você pode tê-los convertidos em instâncias tipadas através de métodos getter / setter.

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // ctor & deserialize methods...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

O exemplo acima desserializará tanto a conta quanto a lista de tarefas em suas respectivas instâncias de classe.





typescript