Как инициализировать объект TypeScript с помощью объекта JSON

Я получаю JSON-объект от AJAX-вызова к REST-серверу. Этот объект имеет имена свойств, которые соответствуют моему классу TypeScript (это продолжение этого вопроса).

Каков наилучший способ его инициализации? Я не думаю, что это будет работать, потому что у класса (& JSON-объект) есть члены, которые являются списками объектов, и члены, которые являются классами, и у этих классов есть члены, которые являются списками и / или классами.

But I'd prefer an approach that looks up the member names and assigns them across, creating lists and instantiating classes as needed, so I don't have to write explicit code for every member in every class (there's a LOT!)

19 ответов

Решение

Вот несколько быстрых снимков, чтобы показать несколько разных способов. Они ни в коем случае не являются "полными", и как заявление об отказе от ответственности, я не думаю, что это хорошая идея сделать это так. Кроме того, код не слишком чистый, так как я набрал его довольно быстро.

Также как примечание: Конечно, десериализуемые классы должны иметь конструкторы по умолчанию, как и во всех других языках, где я знаю о десериализации любого рода. Конечно, Javascript не будет жаловаться, если вы вызовете конструктор не по умолчанию без аргументов, но тогда класс лучше подготовиться к нему (плюс, на самом деле это не будет "типизированный способ").

Вариант № 1: нет информации во время выполнения вообще

Проблема с этим подходом состоит главным образом в том, что имя любого члена должно соответствовать его классу. Это автоматически ограничивает вас одним членом одного типа на класс и нарушает несколько правил хорошей практики. Я настоятельно рекомендую против этого, но просто перечислите это здесь, потому что это был первый "черновик", когда я написал этот ответ (именно поэтому и имена "Foo" и т. Д.).

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

Вариант № 2: имя свойства

Чтобы избавиться от проблемы в варианте № 1, нам нужна некоторая информация о типе узла в объекте JSON. Проблема в том, что в Typescript эти вещи являются конструкциями времени компиляции, и они нужны нам во время выполнения, но объекты времени выполнения просто не знают о своих свойствах, пока они не установлены.

Один из способов сделать это - сделать так, чтобы классы знали их имена. Это свойство также необходимо в JSON. На самом деле, вам нужно только это в 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);

Вариант № 3: явное указание типов элементов

Как указано выше, информация о типах членов класса недоступна во время выполнения - то есть, если мы не сделаем ее доступной. Нам нужно сделать это только для не примитивных членов, и мы готовы пойти:

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

Вариант № 4: подробный, но аккуратный способ

Обновление 01/03/2016: Как отметил @GameAlchemist в комментариях, начиная с Typescript 1.7, решение, описанное ниже, может быть написано лучше с использованием декораторов классов / свойств.

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

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

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

Ты можешь использовать Object.assign Я не знаю, когда это было добавлено, в настоящее время я использую Typescript 2.0.2, и это похоже на функцию ES6.

client.fetch( '' ).then( response => {
        return response.json();
    } ).then( json => {
        let hal : HalJson = Object.assign( new HalJson(), json );
        log.debug( "json", hal );

вот HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

export interface Links {
    readonly [text: string]: Link;
}

export interface Link {
    readonly href: URL;
}

вот что говорит хром

HalJson {_links: Object}
_links
:
Object
public
:
Object
href
:
"http://localhost:9000/v0/public

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

TLDR: TypedJSON (рабочее доказательство концепции)


Корень сложности этой проблемы в том, что нам нужно десериализовать JSON во время выполнения, используя информацию о типах, которая существует только во время компиляции. Это требует, чтобы информация о типах была как-то доступна во время выполнения.

К счастью, это можно решить очень элегантным и надежным способом с помощью декораторов и ReflectDecorators:

  1. Используйте декораторы свойств для свойств, которые подлежат сериализации, для записи информации метаданных и сохранения этой информации где-нибудь, например, в прототипе класса
  2. Передайте эту информацию метаданных рекурсивному инициализатору (десериализатору).

Тип записи-информация

С помощью комбинации ReflectDecorators и декораторов свойств можно легко записать информацию о типе свойства. Элементарная реализация этого подхода будет:

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

Для любого данного свойства приведенный выше фрагмент добавит ссылку на функцию-конструктор свойства к скрытому __propertyTypes__ Свойство на прототипе класса. Например:

class Language {
    @JsonMember // String
    name: string;

    @JsonMember// Number
    level: number;
}

class Person {
    @JsonMember // String
    name: string;

    @JsonMember// Language
    language: Language;
}

И все, у нас есть требуемая информация о типе во время выполнения, которую теперь можно обрабатывать.

Тип обработки информации

Сначала нам нужно получить Object пример использования JSON.parse - после этого мы можем перебрать все в __propertyTypes__ (собранные выше) и создать соответствующие свойства соответственно. Тип корневого объекта должен быть указан, чтобы десериализатор имел отправную точку.

Опять же, мертвой простой реализацией этого подхода будет:

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

Приведенная выше идея имеет большое преимущество десериализации ожидаемыми типами (для сложных / объектных значений) вместо того, что присутствует в JSON. Если Person ожидается, то это Person экземпляр, который создан. С некоторыми дополнительными мерами безопасности для примитивных типов и массивов этот подход можно сделать безопасным, который противостоит любому вредоносному JSON.

Краевые чехлы

Однако, если вы теперь довольны тем, что решение настолько простое, у меня есть плохие новости: существует огромное количество крайних случаев, о которых нужно позаботиться. Только некоторые из которых:

  • Массивы и элементы массива (особенно во вложенных массивах)
  • Полиморфизм
  • Абстрактные классы и интерфейсы
  • ...

Если вы не хотите возиться со всем этим (держу пари, что нет), я был бы рад порекомендовать рабочую экспериментальную версию проверки концепции, использующей этот подход, TypedJSON - которую я создал чтобы решить именно эту проблему, проблема, с которой я сталкиваюсь каждый день.

Из-за того, что декораторы все еще считаются экспериментальными, я бы не рекомендовал использовать их для производственного использования, но до сих пор это хорошо мне помогало.

Я создал инструмент, который генерирует интерфейсы TypeScript и "карту типов" во время выполнения для выполнения проверки типов во время выполнения по результатам JSON.parse: https://ts.quicktype.io/

Например, учитывая этот JSON:

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

quicktype создает следующий интерфейс TypeScript и карту типов:

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

Затем мы проверяем результат JSON.parse по типу карты:

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

Я пропустил некоторый код, но вы можете попробовать quicktype для деталей.

Я использовал этого парня, чтобы сделать работу: https://github.com/weichx/cerialize

Это очень просто, но мощно. Поддерживает:

  • Сериализация и десериализация целого дерева объектов.
  • Постоянные и временные свойства одного и того же объекта.
  • Крючки для настройки (де) логики сериализации.
  • Он может (де) сериализоваться в существующий экземпляр (отлично подходит для Angular) или генерировать новые экземпляры.
  • и т.п.

Пример:

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

Для простых объектов мне нравится этот метод:

class Person {
  constructor(
    public id: String, 
    public name: String, 
    public title: String) {};

  static deserialize(input:any): Person {
    return new Person(input.id, input.name, input.title);
  }
}

var person = Person.deserialize({id: 'P123', name: 'Bob', title: 'Mr'});

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

Это дает вам типизированный объект (против всех ответов, которые используют Object.assign или какой-либо вариант, который дает вам объект) и не требует внешних библиотек или декораторов.

Это мой подход (очень простой):

const jsonObj: { [key: string]: any } = JSON.parse(jsonStr);

for (const key in jsonObj) {
  if (!jsonObj.hasOwnProperty(key)) {
    continue;
  }

  console.log(key); // Key
  console.log(jsonObj[key]); // Value
  // Your logic...
}

если вы хотите безопасности типов и не любите декораторов

      abstract class IPerson{
  name?: string;
  age?: number;
}
class Person extends IPerson{
  constructor({name, age}: IPerson){
    super();
    this.name = name;
    this.age = age;
  }
}

const json = {name: "ali", age: 80};
const person = new Person(json);

Вариант № 5: Использование конструкторов Typescript и jQuery.extend

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

Нет необходимости создавать интерфейсы или перечислять свойства в конструкторе.

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

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

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}

Лучшее , что я нашел для этой цели - это класс-трансформер. github.com/typestack/class-transformer

Вот как вы это используете:

Какой-то класс:

export class Foo {

    name: string;

    @Type(() => Bar)
    bar: Bar;

    public someFunction = (test: string): boolean => {
        ...
    }
}


import { plainToClass } from 'class-transformer';

export class SomeService {

  anyFunction() {
u = plainToClass(Foo, JSONobj);
 }

Если вы используете декоратор @Type, будут созданы и вложенные свойства.

JQuery .extend делает это для вас:

var mytsobject = new mytsobject();

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

$.extend(mytsobject, newObj); //mytsobject will now contain a & b

Четвертый вариант, описанный выше, является простым и приятным способом сделать это, который должен быть объединен со вторым параметром в случае, когда вам нужно обрабатывать иерархию классов, как, например, список членов, который является любым из вхождений подклассов Суперкласс Участника, например, Директор расширяет Участника или Студент расширяет Участника. В этом случае вы должны указать тип подкласса в формате json.

Возможно не актуальное, но простое решение:

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

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

работать на сложные зависимости тоже!!!

Я лично предпочитаю вариант №3 @Ingo Bürk. И я улучшил его коды, чтобы поддерживать массив сложных данных и массив примитивных данных.

interface IDeserializable {
  getTypes(): Object;
}

class Utility {
  static deserializeJson<T>(jsonObj: object, classType: any): T {
    let instanceObj = new classType();
    let types: IDeserializable;
    if (instanceObj && instanceObj.getTypes) {
      types = instanceObj.getTypes();
    }

    for (var prop in jsonObj) {
      if (!(prop in instanceObj)) {
        continue;
      }

      let jsonProp = jsonObj[prop];
      if (this.isObject(jsonProp)) {
        instanceObj[prop] =
          types && types[prop]
            ? this.deserializeJson(jsonProp, types[prop])
            : jsonProp;
      } else if (this.isArray(jsonProp)) {
        instanceObj[prop] = [];
        for (let index = 0; index < jsonProp.length; index++) {
          const elem = jsonProp[index];
          if (this.isObject(elem) && types && types[prop]) {
            instanceObj[prop].push(this.deserializeJson(elem, types[prop]));
          } else {
            instanceObj[prop].push(elem);
          }
        }
      } else {
        instanceObj[prop] = jsonProp;
      }
    }

    return instanceObj;
  }

  //#region ### get types ###
  /**
   * check type of value be string
   * @param {*} value
   */
  static isString(value: any) {
    return typeof value === "string" || value instanceof String;
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isNumber(value: any) {
    return typeof value === "number" && isFinite(value);
  }

  /**
   * check type of value be array
   * @param {*} value
   */
  static isArray(value: any) {
    return value && typeof value === "object" && value.constructor === Array;
  }

  /**
   * check type of value be object
   * @param {*} value
   */
  static isObject(value: any) {
    return value && typeof value === "object" && value.constructor === Object;
  }

  /**
   * check type of value be boolean
   * @param {*} value
   */
  static isBoolean(value: any) {
    return typeof value === "boolean";
  }
  //#endregion
}

// #region ### Models ###
class Hotel implements IDeserializable {
  id: number = 0;
  name: string = "";
  address: string = "";
  city: City = new City(); // complex data
  roomTypes: Array<RoomType> = []; // array of complex data
  facilities: Array<string> = []; // array of primitive data

  // getter example
  get nameAndAddress() {
    return `${this.name} ${this.address}`;
  }

  // function example
  checkRoom() {
    return true;
  }

  // this function will be use for getting run-time type information
  getTypes() {
    return {
      city: City,
      roomTypes: RoomType
    };
  }
}

class RoomType implements IDeserializable {
  id: number = 0;
  name: string = "";
  roomPrices: Array<RoomPrice> = [];

  // getter example
  get totalPrice() {
    return this.roomPrices.map(x => x.price).reduce((a, b) => a + b, 0);
  }

  getTypes() {
    return {
      roomPrices: RoomPrice
    };
  }
}

class RoomPrice {
  price: number = 0;
  date: string = "";
}

class City {
  id: number = 0;
  name: string = "";
}
// #endregion

// #region ### test code ###
var jsonObj = {
  id: 1,
  name: "hotel1",
  address: "address1",
  city: {
    id: 1,
    name: "city1"
  },
  roomTypes: [
    {
      id: 1,
      name: "single",
      roomPrices: [
        {
          price: 1000,
          date: "2020-02-20"
        },
        {
          price: 1500,
          date: "2020-02-21"
        }
      ]
    },
    {
      id: 2,
      name: "double",
      roomPrices: [
        {
          price: 2000,
          date: "2020-02-20"
        },
        {
          price: 2500,
          date: "2020-02-21"
        }
      ]
    }
  ],
  facilities: ["facility1", "facility2"]
};

var hotelInstance = Utility.deserializeJson<Hotel>(jsonObj, Hotel);

console.log(hotelInstance.city.name);
console.log(hotelInstance.nameAndAddress); // getter
console.log(hotelInstance.checkRoom()); // function
console.log(hotelInstance.roomTypes[0].totalPrice); // getter
// #endregion

Еще один вариант использования заводов

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

использовать как это

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. делает ваши занятия простыми
  2. инъекции доступны для заводов для гибкости

Мой подход немного другой. Я не копирую свойства в новые экземпляры, я просто меняю прототип существующих POJO (может не работать в старых браузерах). Каждый класс отвечает за предоставление метода SetPrototypes для установки прототипов любых дочерних объектов, которые, в свою очередь, предоставляют свои собственные методы SetPrototypes.

(Я также использую свойство _Type для получения имени класса неизвестных объектов, но здесь это можно игнорировать)

class ParentClass
{
    public ID?: Guid;
    public Child?: ChildClass;
    public ListOfChildren?: ChildClass[];

    /**
     * Set the prototypes of all objects in the graph.
     * Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
     * @param pojo Plain object received from API/JSON to be given the class prototype.
     */
    private static SetPrototypes(pojo: ParentClass): void
    {
        ObjectUtils.SetPrototypeOf(pojo.Child, ChildClass);
        ObjectUtils.SetPrototypeOfAll(pojo.ListOfChildren, ChildClass);
    }
}

class ChildClass
{
    public ID?: Guid;
    public GrandChild?: GrandChildClass;

    /**
     * Set the prototypes of all objects in the graph.
     * Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
     * @param pojo Plain object received from API/JSON to be given the class prototype.
     */
    private static SetPrototypes(pojo: ChildClass): void
    {
        ObjectUtils.SetPrototypeOf(pojo.GrandChild, GrandChildClass);
    }
}

Вот ObjectUtils.ts:

/**
 * ClassType lets us specify arguments as class variables.
 * (where ClassType == window[ClassName])
 */
type ClassType = { new(...args: any[]): any; };

/**
 * The name of a class as opposed to the class itself.
 * (where ClassType == window[ClassName])
 */
type ClassName = string & {};

abstract class ObjectUtils
{
/**
 * Set the prototype of an object to the specified class.
 *
 * Does nothing if source or type are null.
 * Throws an exception if type is not a known class type.
 *
 * If type has the SetPrototypes method then that is called on the source
 * to perform recursive prototype assignment on an object graph.
 *
 * SetPrototypes is declared private on types because it should only be called
 * by this method. It does not (and must not) set the prototype of the object
 * itself - only the protoypes of child properties, otherwise it would cause a
 * loop. Thus a public method would be misleading and not useful on its own.
 * 
 * https://stackru.com/questions/9959727/proto-vs-prototype-in-javascript
 */
public static SetPrototypeOf(source: any, type: ClassType | ClassName): any
{
    let classType = (typeof type === "string") ? window[type] : type;

    if (!source || !classType)
    {
        return source;
    }

    // Guard/contract utility
    ExGuard.IsValid(classType.prototype, "type", <any>type);

    if ((<any>Object).setPrototypeOf)
    {
        (<any>Object).setPrototypeOf(source, classType.prototype);
    }
    else if (source.__proto__)
    {
        source.__proto__ = classType.prototype.__proto__;
    }

    if (typeof classType["SetPrototypes"] === "function")
    {
        classType["SetPrototypes"](source);
    }

    return source;
}

/**
 * Set the prototype of a list of objects to the specified class.
 * 
 * Throws an exception if type is not a known class type.
 */
public static SetPrototypeOfAll(source: any[], type: ClassType): void
{
    if (!source)
    {
        return;
    }

    for (var i = 0; i < source.length; i++)
    {
        this.SetPrototypeOf(source[i], type);
    }
}
}

Применение:

let pojo = SomePlainOldJavascriptObjectReceivedViaAjax;

let parentObject = ObjectUtils.SetPrototypeOf(pojo, ParentClass);

// parentObject is now a proper ParentClass instance

Подобно подходу @young-ceo, я разработал следующее решение:

      function reify(value: any): any {
  if (value === null) {
    return null;
  }

  if (Array.isArray(value)) {
    return value.map(reify);
  }

  if (typeof value === 'object') {
    var result: any = {};
    for (const key in value) {
      result = { [key]: reify(value[key]), ...result };
    }
    return result;
  }

  if (typeof value === 'string') {
    if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
      return new Date(value);
    }
  }

  return value;
}

// example usage:
var jsonResult = await response.json();
return reify(jsonResult);
**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;
    }
}

Вы можете сделать как ниже

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

а также

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
Другие вопросы по тегам