Como faço para converter um object JSON para uma class de typescript

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

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

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

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

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

Alguns exemplos de copiar os dados:

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

Em essência, você apenas:

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

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 nesteds, mas você tem que decorar o membro da sua turma.

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

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

Onde 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 isso ajude!

Eu encontrei um artigo muito interessante sobre fundição genérica de JSON para uma class de datilografia:

http://cloudmark.github.io/Json-Mapping/

Você acaba com o seguinte código:

 let example = { "name": "Mark", "surname": "Galea", "age": 30, "address": { "first-line": "Some where", "second-line": "Over Here", "city": "In This City" } }; MapUtils.deserialize(Person, example); // custom class 

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 object ninho, infelizmente.

Supondo que o json possua as mesmas propriedades da sua class de datilografia, você não precisa copiar suas propriedades do Json para o seu object datilografado. Você apenas terá que construir seu object 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 ... } 

Para fazer esse trabalho:

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

2) Observe que você terá que fazer o mesmo para objects vinculados. No caso de Funcionários no exemplo, você também cria um construtor usando a parte dos dados do json para os funcionários. Você chama $ .map para traduzir os funcionários do json para objects 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); } } 

Esta é a melhor solução que encontrei quando lidei com classs e objects json.

No meu caso isso funciona. Eu usei funções Object.assign (target, sources …) . Primeiro, a criação do object correto, copia os dados do object 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; } } 

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 pode inapropriadamente distribuir sua instância de class com propriedades irrelevantes (assim como fechamentos definidos) que não foram declaradas dentro da própria class.

Na class 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 class 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 class 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 , exemplo:

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

Se você tem membros de class que são de um tipo específico (também conhecido como: instance of another class), então você pode tê-los convertidos em instâncias digitadas através de methods 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 class.

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

Considerando a seguinte interface e um object 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 casting 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 = json; 

C. Elenco dynamic avançado, você verifica a estrutura do object

 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 function 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 object quando necessário. Por enquanto, você poderia usar um validador de esquema json cuja finalidade é aproximadamente a mesma OU esse gerador de function de verificação de tipo de tempo de execução

Enquanto não estiver lançando por dizer; Eu encontrei https://github.com/JohnWhiteTB/TypedJSON para ser uma alternativa útil.

 @JsonObject class Person { @JsonMember firstName: string; @JsonMember lastName: string; public getFullname() { return this.firstName + " " + this.lastName; } } var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person); person instanceof Person; // true person.getFullname(); // "John Doe" 

Eu criei uma ferramenta simples para fazer isso https://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

Uma pergunta antiga com respostas corretas, mas não muito eficientes. Isso que eu proponho:

Crie uma class base que contenha o método init () e os methods de conversão estáticos (para um único object e uma matriz). Os methods estáticos podem estar em qualquer lugar; a versão com a class base e init () permite extensões fáceis depois.

 export class ContentItem { // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem) static castAs(doc: T, proto: typeof ContentItem): T { // if we already have the correct class skip the cast if (doc instanceof proto) { return doc; } // create a new object (create), and copy over all properties (assign) const d: T = Object.create(proto.prototype); Object.assign(d, doc); // reason to extend the base class - we want to be able to call init() after cast d.init(); return d; } // another method casts an array static castAllAs(docs: T[], proto: typeof ContentItem): T[] { return docs.map(d => ContentItem.castAs(d, proto)); } init() { } } 

Mecânica semelhante (com assign () ) foi mencionada no post @ Adam111p. Apenas outra maneira (mais completa) de fazer isso. @ Timothy Perez é crítico em relação a assign () , mas isso é totalmente apropriado aqui.

Implemente uma class derivada (o real):

 import { ContentItem } from './content-item'; export class SubjectArea extends ContentItem { id: number; title: string; areas: SubjectArea[]; // contains embedded objects depth: number; // method will be unavailable unless we use cast lead(): string { return '. '.repeat(this.depth); } // in case we have embedded objects, call cast on them here init() { if (this.areas) { this.areas = ContentItem.castAllAs(this.areas, SubjectArea); } } } 

Agora podemos converter um object recuperado do serviço:

 const area = ContentItem.castAs(docFromREST, SubjectArea); 

Toda a hierarquia de objects SubjectArea terá a class correta.

Um caso de uso / exemplo; crie um serviço Angular (class base abstrata novamente):

 export abstract class BaseService { BASE_URL = 'http://host:port/'; protected abstract http: Http; abstract path: string; abstract subClass: typeof ContentItem; cast(source: T): T { return ContentItem.castAs(source, this.subClass); } castAll(source: T[]): T[] { return ContentItem.castAllAs(source, this.subClass); } constructor() { } get(): Promise { const value = this.http.get(`${this.BASE_URL}${this.path}`) .toPromise() .then(response => { const items: T[] = this.castAll(response.json()); return items; }); return value; } } 

O uso se torna muito simples; criar um serviço de área:

 @Injectable() export class SubjectAreaService extends BaseService { path = 'area'; subClass = SubjectArea; constructor(protected http: Http) { super(); } } 

O método get () do serviço retornará um Promise de um array já convertido como objects SubjectArea (hierarquia completa)

Agora digamos, temos outra class:

 export class OtherItem extends ContentItem {...} 

Criar um serviço que recupera dados e lança para a class correta é tão simples quanto:

 @Injectable() export class OtherItemService extends BaseService { path = 'other'; subClass = OtherItem; constructor(protected http: Http) { super(); } } 

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

  

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

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

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

Não vejo nenhuma menção ao json-typescript-mapper: https://www.npmjs.com/package/json-typescript-mapper . Parece uma combinação de @ PhilipMiglinci’s find, e @ Pak’s answer, até onde eu sei.

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