уроки - Как сделать объект JSON классом типов




создать json (11)

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

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

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


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

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

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

Надеюсь, это поможет!


В моем случае это работает. Я использовал функции 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;
  }
}

Если вы используете 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()
})

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


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

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


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

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

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

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

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

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

2) Обратите внимание, что вам придется делать то же самое для связанных объектов. В случае Employees в этом примере вы также создаете конструктор, который принимает часть данных json для сотрудников. Вы вызываете $ .map для перевода сотрудников json на машинописные объекты 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);
    }
}

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


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

Создайте базовый класс, который содержит метод 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 будет иметь правильный класс.

Пример использования / пример; создать Угловую службу (абстрактный базовый класс):

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

Хотя это не кастинг за слово; Я нашел 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"

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

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

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

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

objectConverted.name

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

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

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


TLDR: один вкладыш

// 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's 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 жалуется на то, что тип аргумента несовместим, просто введите аргумент в тот же тип, используя угловые скобки <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, так и список задач в соответствующие экземпляры классов.







typescript