Як ініціалізувати об’єкт TypeScript з об'єктом JSON


199

Я отримую об'єкт JSON від дзвінка AJAX на сервер REST. У цього об’єкта є назви властивостей, які відповідають моєму класу TypeScript (це подальше запитання до цього питання ).

Який найкращий спосіб його ініціалізувати? Я не думаю, що це буде працювати, тому що у класу (& JSON об'єкта) є члени, які є списками об'єктів та члени, які є класами, а в цих класах є члени, які є списками та / або класами.

Але я вважаю за краще підхід, який шукає імена членів і призначає їх поперек, створюючи списки та інстанціюючи класи, якщо це потрібно, тому мені не потрібно писати явний код для кожного члена в кожному класі (є ЛОТО!)


1
Чому ви знову це запитували (як відповідь, яку я надавав в іншому запитанні, говорив, що це не працюватиме, і це стосується копіювання властивостей у існуючий об’єкт)?
WiredPrairie


3
@WiredPrairie це питання відрізняється, він запитує, чи можу я пройтися по властивостях по черзі та призначити їх поперек. Іншими питаннями було питання, чи можу я це зробити?
Девід Тілен

1
@ WiredPrairie продовження: Якщо ви продовжуєте занурюватися у властивості, поки не дістанетесь лише до примітивних типів, їх можна призначити поперек.
Девід Тілен

2
Це все ще копіює всі значення так само, як я запропонував вам зробити. Немає нового способу зробити це в TypeScript, оскільки це фундаментальна конструкція JavaScript. Для великих об'єктів, можливо, ви не хочете копіювати жодні значення, а просто "діяти" на структуру даних.
WiredPrairie

Відповіді:


189

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

Також як зауваження: Звичайно, у десериалізаційних класів повинні бути конструктори за замовчуванням, як це відбувається у всіх інших мовах, де мені відомо про десеріалізацію будь-якого типу. Звичайно, Javascript не поскаржиться, якщо ви викликаєте конструктор, що не працює за замовчуванням, без аргументів, але клас краще тоді бути готовим до цього (плюс, це насправді не був би "typecripty way").

Варіант №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 ) ) , рішення, описане нижче, може бути написано краще, використовуючи декоратори класу / властивості.

Серіалізація - це завжди проблема, і, на мою думку, найкращий спосіб - це спосіб, який просто не найкоротший. З усіх варіантів я б хотів цього, тому що автор класу має повний контроль над станом десеріалізованих об'єктів. Якби мені довелося здогадуватися, я б сказав, що всі інші варіанти рано чи пізно приведуть вас у біду (якщо тільки 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);

12
Варіант №4 - це те, що я б назвав розумним шляхом. Ще потрібно написати код десеріалізації, але він у тому ж класі і повністю керований. Якщо ви приїжджаєте з Java, то це можна порівняти з необхідністю писати equalsабо toStringметодами (лише те, що вони зазвичай автоматично створюються). Воно не повинно бути занадто важко написати генератор для , deserializeякщо ви хочете, але це просто не може бути час виконання автоматизації.
Інго Бюрк

2
@ IngoBürk, я знаю, що я задаю це питання через 2 роки, але як це буде працювати над масивом об'єктів? Зразок вищевказаного коду добре працює для об'єкта JSON як його можна використовувати для масиву об’єктів?
Пратік Гайквад

2
Побічне зауваження: починаючи з 1.7, (правда, новішої, ніж ваша відповідь), typecript надає декоратори класу / властивості, що дозволяє писати 4-те рішення в охайному вигляді.
ГраАльхімік

1
Краща документація я знайшов це StackOverflow відповідь stackoverflow.com/a/29837695/856501 . Я використовував декораторів у своєму проекті, і хоча мені хотілося б кілька інших особливостей, я повинен сказати, що вони працюють як шарм.
GameAlchemist

2
Я б ще не стрибав на декораторів для виробничого проекту - майте на увазі, що вони все ще є експериментальною особливістю. Я не буду базувати код реального світу на "експериментах", тому що, наскільки ми стурбовані, вони можуть перейти в наступну версію, і вам доведеться переписати купу коду або назавжди застрягти на старій версії TS. Тільки мій $ 02.
RVP

35

ви можете використовувати, 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

тож ви бачите, що це призначення не робить рекурсивно


2
так, в принципі, це: Object.assign. Чому ми маємо дві відповіді, схожі на лексикону, вище цього?
phil294

18
@Blauhim Тому, що Object.assignвін не буде працювати рекурсивно і не інстанціювати правильні типи об'єктів, залишаючи значення як Objectекземпляри. Хоча це добре для тривіальних завдань, комплексна серіалізація з ним неможлива. Наприклад, якщо властивість класу має тип власного класу, JSON.parse+ Object.assignінстанціює це властивість до Object. Побічні ефекти включають відсутні методи та засоби доступу.
Джон Вайш

@JohnWeisz клас класу об'єктів верхнього рівня має правильний тип, і я згадав про рекурсивну річ у цьому ... що сказав, YMMV, і це можуть бути вимикачі угод.
ксенотерацид

Цитується безпосередньо з питання: "у класі є члени, які є списками об'єктів та члени, які є класами, а в цих класах є члени, які є списками та / або класами [...] Я вважаю за краще підхід, який шукає члена імена та призначає їх поперек, створюючи списки та інстанціювання класів за потребою, тому мені не потрібно писати явний код для кожного члена у кожному класі " - що не в тому випадку Object.assign, де він все ще зводиться до написання вкладеної інстанції рукою. Цей підхід чудово підходить для дуже простих об'єктів на рівні уроків, але не для реального використання.
Джон Вайш

@JohnWeisz впевнений, що в основному відповів на це, оскільки це не було в жодних відповідях і здавалося простим для деяких випадків використання. Я впевнений, що його можна використовувати також у поєднанні з іншими відповідями, такими як рефлексія, для того, щоб робити те, що ви шукаєте. Я також написав це частково, щоб потім згадати про це. З огляду на ці відповіді та використання та написання набагато потужніших бібліотек, здається, немає нічого доступного для "реального використання".
ксенотеррацид

34

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- після цього ми можемо перебирати на entires __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. Якщо a Personочікується, то це створюється Personекземпляр. Завдяки застосуванню додаткових заходів безпеки для примітивних типів та масивів, цей підхід може бути захищеним, що протистоїть будь-якому шкідливому JSON.

 

Крайові випадки

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

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

Якщо ви не хочете поспішати з усім цим (я думаю, що ви цього не робите), я радий порекомендувати робочу експериментальну версію перевірки концепції, що використовує цей підхід, TypedJSON - який я створив щоб вирішити цю точну проблему - проблему, з якою я стикаюся щодня.

Через те, як декоратори досі вважаються експериментальними, я б не рекомендував використовувати його для використання у виробництві, але поки що він мені добре послужив.


TypedJSON працював чудово; велике спасибі за довідку.
Ніл

Чудова робота, ви придумали дуже елегантне рішення проблеми, яка мене тривалий час хвилює. Я буду дуже уважно стежити за вашим проектом!
Джон Стріклер

12

Я використовую цього хлопця для виконання роботи: 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);

6

Я створив інструмент, що генерує інтерфейси TypeScript та "мапу типу" для виконання для перевірки набору тексту виконання під час результатів JSON.parse: 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 для деталей.


1
Після багатогодинних досліджень та спробування моїх рук у кількох техніках розбору, я можу сказати, що це відмінне рішення - головним чином тому, що декоратори досі експериментальні. * Оригінальне посилання для мене порушено; але ts.quicktype.io працює. * Перетворення JSON на схему JSON - це хороший перший крок.
LexieHankins

3

Варіант №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()
}

звідки $.extendберуться?
whale_steward

@whale_steward Я б припустив, що автор посилається на бібліотеку jQuery. У світі JavaScript "$" дуже часто користується jQuery.
Нік Рот

як його імпортувати? достатньо включити його на html голову?
whale_steward

так, я оновлюю відповідь, щоб замінити $ jQuery. імпортуйте jQuery.js у голову html та встановіть та додайте @ type / jquery у свій package.json, розділ devDependitions.
Ентоні Бренельє

1
Зауважте, що в Javascript ви повинні це зробити Object.assign, що усуває цю залежність від jQuery.
Леон Пелтьє

2

Для простих об'єктів мені подобається цей метод:

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 або якийсь варіант, який дає вам Об'єкт) і не потребують зовнішніх бібліотек чи декораторів.


1

Описаний вище четвертий варіант - це простий і приємний спосіб зробити це, який повинен поєднуватися з другим варіантом у тому випадку, коли вам доведеться обробляти ієрархію класів, як, наприклад, список членів, який є будь-яким випадком виникнення підкласів супер клас класу, наприклад директор продовжує член або студент продовжує член. У такому випадку вам слід надати тип підкласу у форматі json


1

JQuery .extend робить це для вас:

var mytsobject = new mytsobject();

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

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

1

найкраще, що я знайшов для цієї мети - клас-трансформер. 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, також будуть створені вкладені властивості.


0

Можливо, не власне, але просте рішення:

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

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

працювати і для важких залежностей !!!


9
Цей підхід насправді не працює так, як очікувалося. Якщо ви перевіряєте результати виконання, bazвони матимуть тип, Objectа не тип. Bar.Він працює в цьому простому випадку, оскільки Barне має методів (просто примітивні властивості). Якби Barтакий метод був подібний isEnabled(), цей підхід був би невдалим, оскільки цей метод не був би в серіалізованій рядку JSON.
Тодд

0

Ще один варіант використання заводів

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. ін'єкцій, доступних заводам для гнучкості

0

Я особисто віддаю перевагу варіант №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

-1

ви можете зробити, як нижче

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

і

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });

Це фактично не створить екземпляр часу очікуваного типу об'єкта. Він буде працювати, коли ваш тип має лише примітивні властивості, але вийде з ладу, коли тип має методи. Визначення інтерфейсу також недоступні під час виконання (лише час збирання).
Тодд

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

зателефонуйте до вмісту, як наведено нижче, наприклад:
user8390810

<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
користувач8390810

Це не здається "[інстанціонувати] класи за потребою".
LexieHankins
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.