Как преобразовать объект JSON в класс машинописи

275

Я прочитал объект JSON с удаленного сервера REST. Этот объект JSON обладает всеми свойствами класса typescript (по дизайну). Как передать полученный JSON-объект типу var?

Я не хочу заполнять var typescript (т.е. иметь конструктор, который принимает этот объект JSON). Он большой и копирует все по суб-объекту по под-объекту и свойству по свойству занимает много времени.

Обновление: Вы можете передать его в интерфейс typescript!

  • 0
    вы можете использовать github.com/vojtechhabarta/typescript-generator для генерации интерфейсов TypeScript в случае, если ваш JSON отображается с использованием классов Java
  • 0
    Я написал небольшую библиотеку для кастинга: sulphur-blog.azurewebsites.net/typescript-mini-cast-library
Показать ещё 5 комментариев
Теги:

17 ответов

115
Лучший ответ

Вы не можете просто передать результат обычного старого JavaScript из запроса Ajax в экземпляр класса прототипа JavaScript/ TypeScript. Существует множество методов для этого и, как правило, связаны с копированием данных. Если вы не создадите экземпляр класса, он не будет иметь никаких методов или свойств. Он останется простым объектом JavaScript.

Хотя, если вы только имели дело с данными, вы могли бы просто сделать приведение к интерфейсу (поскольку это чисто структура времени компиляции), для этого потребуется, чтобы вы использовали класс TypeScript, который использует экземпляр данных и выполняет операции с этими данными.

Некоторые примеры копирования данных:

В сущности, вы просто:

var d = new MyRichObject();
d.copyInto(jsonResult);
  • 0
    Я согласен с вашим ответом. Кроме того, хотя сейчас я не в состоянии найти и протестировать его, я думаю, что эти два шага можно объединить, предоставив функцию пробуждения в качестве параметра JSON.parse() . И то, и другое еще нужно сделать, но синтаксически их можно объединить.
  • 0
    Конечно, это тоже может сработать - у меня нет ощущения, будет ли это более эффективным, хотя для каждого свойства потребуется вызывать дополнительный вызов функции.
Показать ещё 7 комментариев
61

У меня была такая же проблема, и я нашел библиотеку, которая выполняет эту работу: https://github.com/pleerock/class-transformer.

Он работает следующим образом:

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

Он поддерживает вложенные дочерние элементы, но вы должны украсить свой член класса.

  • 7
    Эта блестящая небольшая библиотека решила ее идеально с минимальными усилиями (не забывайте, однако, о ваших аннотациях @Type ). Этот ответ заслуживает большего доверия.
  • 2
    этот материал золотой
Показать ещё 6 комментариев
40

В TypeScript вы можете сделать утверждение типа с помощью интерфейса и таких дженериков:

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

Где ILocationMap описывает форму ваших данных. Преимущество этого метода заключается в том, что ваш JSON может содержать больше свойств, но форма удовлетворяет условиям интерфейса.

Я надеюсь, что это поможет!

  • 39
    К вашему сведению: это утверждение типа, а не актерский состав.
  • 5
    Смотрите здесь для различия между утверждением типа и приведением .
Показать ещё 3 комментария
23

Я нашел очень интересную статью о родовом литье JSON для класса Typescript:

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

В итоге вы получите следующий код:

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
21

Если вы используете ES6, попробуйте следующее:

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

Но этот путь не будет работать на объект гнезда, к сожалению.

  • 0
    Они просили об этом в Typescript.
  • 0
    HI @ joe.feser, я упоминаю ES6, потому что таким образом требуется метод 'Object.assign'.
Показать ещё 1 комментарий
17

Предполагая, что json обладает теми же свойствами, что и ваш класс typescript, вам не нужно копировать свои свойства Json в ваш объект typescript. Вам просто нужно построить объект typescript, передающий json-данные в конструкторе.

В своем обратном вызове ajax вы получаете компанию:

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

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

In, чтобы сделать эту работу:

1) Добавьте конструктор в свой класс typescript, который принимает данные json в качестве параметра. В этом конструкторе вы расширяете свой json-объект с помощью jQuery, например: $.extend( this, jsonData). $.extend позволяет сохранять прототипы javascript при добавлении свойств объекта json.

2) Обратите внимание, что вам придется делать то же самое для связанных объектов. В случае Employees в этом примере вы также создаете конструктор, который принимает часть данных json для сотрудников. Вы вызываете $.map для перевода сотрудников json на объекты typescript Employee.

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

Это лучшее решение, которое я нашел при работе с typescript классами и объектами json.

  • 0
    Я предпочитаю это решение, а не реализацию и поддержание интерфейсов, потому что мои приложения Angular2 имеют реальную модель приложения, которая может отличаться от модели веб-сервисов, которые использует мое приложение. Он может иметь частные данные и рассчитанные свойства.
  • 4
    Использование JQuery в проектах Angular - ужасная идея. И если ваши модели содержат множество функций, они больше не являются моделями.
Показать ещё 3 комментария
11

TL;DR: один вкладыш

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

Подробный ответ

Я бы не рекомендовал подход Object.assign, поскольку он может ненадлежащим образом помещать ваш экземпляр класса нерелевантными свойствами (а также определенными закрытиями), которые не были объявлены внутри самого класса.

В классе, который вы пытаетесь десериализовать, я бы гарантировал, что будут определены любые свойства, которые вы хотите десериализовать (нуль, пустой массив и т.д.). Определив свои свойства с помощью начальных значений, вы обнаружите их видимость при попытке итерации членов класса для назначения значений (см. Ниже метод десериализации).

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 declared, but not defined
    const keys = Object.keys(this);

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

В приведенном выше примере я просто создал метод десериализации. В реальном мире я бы централизовал его в многоразовом базовом классе или методе обслуживания.

Вот как использовать это в чем-то вроде http resp...

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

Если tslint/ide жалуется на то, что тип аргумента несовместим, просто введите аргумент в тот же тип, используя angular скобки <YourClassName>, например:

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

Если у вас есть члены класса определенного типа (например: экземпляр другого класса), вы можете включить их в строковые экземпляры с помощью методов 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));
  }
}

В приведенном выше примере десериализуются как acct, так и список задач в соответствующие экземпляры классов.

  • 1
    Мне нравится однострочник ;-)
  • 0
    Я получаю это сообщение об ошибке: Тип «{имя: строка, возраст: номер, идентификатор: номер}» не может быть преобразован в тип «Персона». Свойство 'id' является закрытым в типе 'Person', но не в типе '{name: string, age: number, id: number}'
Показать ещё 4 комментария
11

В моем случае это работает. Я использовал функции Object.assign(target, sources...). Во-первых, создание правильного объекта, затем копирует данные из объекта json в target.Example:

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

И более продвинутый пример использования. Пример использования массива.

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;
  }
}
  • 0
    Что происходит, когда у вас есть частная собственность?
  • 0
    Потому что объект jsonUser не является классом User. Без операции Object.assign (u, jsonUsers); Вы не можете использовать функцию getId (). Только после назначения вы получаете действительный объект User, в котором вы можете использовать функцию getId (). Функция getId () предназначена только для примера того, что операция прошла успешно.
Показать ещё 3 комментария
5

Автоматически проверять, есть ли у объекта JSON, полученного с сервера, ожидаемые (читай, соответствуют) свойства интерфейса машинописного текста, пока нечем. Но вы можете использовать Определяемые пользователем Стражи Типа

Учитывая следующий интерфейс и глупый объект json (это мог быть любой тип):

interface MyInterface {
    key: string;
 }

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

Три возможных способа:

A. Утверждение типа или простое статическое приведение после переменной

const myObject: MyInterface = json as MyInterface;

B. Простое статическое приведение, перед переменной и между алмазами

const myObject: MyInterface = <MyInterface>json;

C. Продвинутый динамический состав, вы сами проверяете структуру объекта

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

Вы можете json; //no error myObject.key; //custom type guard function isMyInterface(json: any): json is MyInterface { return json.key !== undefined; } if (isMyInterface(json)) { //no error, the type is inferred by our custom type guard //in short, we are sure json conforms to MyInterface at this point json.key; } else { //Fallback if the dynamic cast did not work throw new Error(`Expected MyInterface, got '${json}'.`); } rel="nofollow noreferrer">поиграть с этим примером здесь

Обратите внимание, что здесь трудность заключается в написании функции isMyInterface. Я надеюсь, что TS рано или поздно добавит декоратор, чтобы экспортировать сложную типизацию во время выполнения и позволить среде выполнения проверять структуру объекта при необходимости. На данный момент вы можете использовать валидатор схемы json, цель которого примерно такая же, ИЛИ этот генератор функций проверки типа во время выполнения.

2

Пока он не произносит ни слова; Я нашел https://github.com/JohnWhiteTB/TypedJSON, чтобы стать полезной альтернативой.

@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"
  • 1
    Это не кастинг, что он на самом деле делает?
1

Я использовал эту библиотеку здесь: https://github.com/pleerock/class-transformer

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

Реализация:

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

Иногда вам нужно будет проанализировать значения JSON для plainToClass, чтобы понять, что это данные в формате JSON

1

Старый вопрос с в основном правильными, но не очень эффективными ответами. Вот что я предлагаю:

Создайте базовый класс, который содержит метод init() и статические методы приведения (для одного объекта и массива). Статические методы могут быть где угодно; версия с базовым классом и init() впоследствии позволяет легко расширяться.

export class ContentItem {
    // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
    static castAs<T extends ContentItem>(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<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

Подобная механика (с assign()) была упомянута в посте @Adam111p. Просто еще один (более полный) способ сделать это. @Timothy Perez критически относится к assign(), но imho он здесь вполне уместен.

Реализуйте производный (настоящий) класс:

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

Теперь мы можем привести объект, полученный из сервиса:

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

Вся иерархия объектов SubjectArea будет иметь правильный класс.

Вариант использования/пример; создайте сервис Angular (снова абстрактный базовый класс):

export abstract class BaseService<T extends ContentItem> {
  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<T[]> {
    const value = this.http.get('${this.BASE_URL}${this.path}')
      .toPromise()
      .then(response => {
        const items: T[] = this.castAll(response.json());
        return items;
      });
    return value;
  }
}

Использование становится очень простым; создать сервис Area:

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

  constructor(protected http: Http) { super(); }
}

Метод get() службы вернет Promise для массива, уже приведенного к объектам SubjectArea (вся иерархия)

Теперь скажите, у нас есть другой класс:

export class OtherItem extends ContentItem {...}

Создать сервис, который извлекает данные и приводит к правильному классу, так же просто, как:

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

  constructor(protected http: Http) { super(); }
}
1

Я создал простой инструмент для этого https://beshanoe.github.io/json2ts/ В отличие от json2ts.com, он работает непосредственно в браузере и не отправляет ваши данные на неизвестные серверы. Также он имеет несколько настроек. Я буду работать над улучшением его функциональности

0

В конце TS вы можете сделать так:

const isMyInterface = (val: any): val is MyInterface => {
  if (!val) { return false; }
  if (!val.myProp) { return false; }
  return true;
};

И чем пользователь, как это:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
0

Это простой и действительно хороший вариант

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

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

И тогда у вас будет

objectConverted.name
0

Я не вижу упоминания о json-typcript-mapper: https://www.npmjs.com/package/json-typescript-mapper. Насколько я могу судить, это выглядит как комбинация поиска @PhilipMiglinci и ответа @Pak.

  • 0
    На самом деле, я попробовал это, но затем возникла странная проблема с моим AuthService, которая не имеет смысла: Uncaught Error: Can't resolve all parameters for AuthService: (?).
0

Одна вещь, которую мы сделали, потому что мы являемся магазином .NET, создаем этот инструмент (https://github.com/Centeva/TypeScripter).

Он строит классы typescript из наших dll. Все, что нам нужно сделать, это передать наш ответ json в класс как параметр. Это работает для нас.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню