Jak zainicjować obiekt TypeScript za pomocą obiektu JSON

198

Otrzymuję obiekt JSON z wywołania AJAX do serwera REST. Ten obiekt ma nazwy właściwości pasujące do mojej klasy TypeScript (jest to kontynuacja tego pytania ).

Jaki jest najlepszy sposób na jego zainicjowanie? Nie sądzę, żeby to zadziałało, ponieważ klasa (i obiekt JSON) ma członków, które są listami obiektów i członków, które są klasami, a te klasy mają członków, które są listami i / lub klasami.

Ale wolałbym podejście, które sprawdza nazwy członków i przypisuje je, tworząc listy i tworząc instancje w razie potrzeby, więc nie muszę pisać jawnego kodu dla każdego członka w każdej klasie (jest DUŻO!)

David Thielen
źródło
1
Dlaczego zadałeś to ponownie (ponieważ odpowiedź, którą podałem w innym pytaniu, powiedziała, że ​​to nie zadziała i że chodziło o kopiowanie właściwości do istniejącego obiektu)?
WiredPrairie
1
możliwy duplikat Jak rzutować obiekt JSON do klasy maszynopisu
WiredPrairie
3
@ WiredPrairie to pytanie jest inne, pyta, czy mogę przejść właściwości jeden po drugim i przypisać je w poprzek. Pozostałe pytania dotyczyły tego, czy mógłbym to rzucić.
David Thielen,
1
@WiredPrairie cd: Jeśli będziesz nurkować w nieruchomościach, dopóki nie dojdziesz tylko do prymitywnych typów, możesz je przypisać.
David Thielen,
2
Nadal kopiuje wszystkie wartości, tak jak zasugerowałem, że musisz to zrobić. Nie ma nowego sposobu na zrobienie tego w TypeScript, ponieważ jest to podstawowy projekt JavaScript. W przypadku dużych obiektów możesz nie chcieć kopiować żadnych wartości i po prostu „działać na” strukturze danych.
WiredPrairie

Odpowiedzi:

188

Oto kilka szybkich ujęć, które pokazują kilka różnych sposobów. Nie są one w żadnym wypadku „kompletne” i jako wyłączenie odpowiedzialności nie wydaje mi się, aby robić to w ten sposób. Również kod nie jest zbyt czysty, ponieważ napisałem go dość szybko.

Również jako uwaga: Oczywiście klasy deserializowalne muszą mieć domyślne konstruktory, jak ma to miejsce we wszystkich innych językach, w których jestem świadomy wszelkiego rodzaju deserializacji. Oczywiście Javascript nie będzie narzekał, jeśli wywołasz konstruktora, który nie jest domyślny bez argumentów, ale lepiej przygotuj się na klasę (plus, tak naprawdę nie byłby to „typowy sposób”).

Opcja nr 1: Brak informacji o czasie wykonywania

Problem z tym podejściem polega głównie na tym, że nazwa dowolnego członka musi pasować do jego klasy. Co automatycznie ogranicza cię do jednego członka tego samego typu na klasę i łamie kilka zasad dobrej praktyki. Zdecydowanie odradzam to, ale po prostu wymień to tutaj, ponieważ był to pierwszy „szkic”, kiedy napisałem tę odpowiedź (dlatego też imiona to „Foo” itp.).

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

Opcja nr 2: właściwość name

Aby pozbyć się problemu w opcji nr 1, potrzebujemy informacji o typie węzła w obiekcie JSON. Problem polega na tym, że w Typescript te rzeczy są konstrukcjami czasu kompilacji i potrzebujemy ich w czasie wykonywania - ale obiekty środowiska wykonawczego po prostu nie mają świadomości swoich właściwości, dopóki nie zostaną ustawione.

Jednym ze sposobów jest uświadomienie klasom ich nazw. Tej właściwości potrzebujesz także w JSON. Właściwie potrzebujesz go tylko w 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);

Opcja nr 3: Jawne określenie typów członków

Jak wspomniano powyżej, informacje o typie członków klasy nie są dostępne w środowisku wykonawczym - to znaczy, chyba że je udostępniamy. Musimy to zrobić tylko dla prymitywnych członków i jesteśmy gotowi:

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

Opcja nr 4: pełna, ale zgrabna droga

Aktualizacja 01.03.2016: Jak zauważyła @GameAlchemist w komentarzach ( pomysł , implementacja ), tak jak w maszynie Typescript 1.7, opisane poniżej rozwiązanie można lepiej napisać przy użyciu dekoratorów klas / właściwości.

Serializacja zawsze stanowi problem i moim zdaniem najlepszym sposobem jest sposób, który po prostu nie jest najkrótszy. Spośród wszystkich opcji wolałbym to, ponieważ autor klasy ma pełną kontrolę nad stanem zdezrializowanych obiektów. Gdybym musiał zgadywać, powiedziałbym, że wszystkie inne opcje, prędzej czy później, wpędzą cię w kłopoty (chyba że Javascript znajdzie sposób na rozwiązanie tego problemu).

Naprawdę, poniższy przykład nie oddaje sprawiedliwości elastyczności. Naprawdę po prostu kopiuje strukturę klasy. Różnica, o której musisz jednak pamiętać, polega na tym, że klasa ma pełną kontrolę nad użyciem dowolnego typu JSON, który chce kontrolować stan całej klasy (możesz obliczyć rzeczy itp.).

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);
Ingo Bürk
źródło
12
Opcja nr 4 to tak zwana rozsądna droga. Nadal musisz napisać kod deserializacji, ale jest on w tej samej klasie i jest w pełni kontrolowany. Jeśli pochodzisz z Javy, jest to porównywalne z koniecznością pisania equalslub toStringmetod (tyle, że zazwyczaj masz je automatycznie generowane). To nie powinno być zbyt trudne, aby napisać generator dla deserializegdybyś chciał, ale to po prostu nie mogą być uruchamiane w czasie automatyki.
Ingo Bürk
2
@ IngoBürk, wiem, że zadaję to pytanie 2 lata później, ale jak to będzie działać na wielu obiektach? Powyższy przykładowy kod działa dobrze dla obiektu JSON. jak można go wykorzystać do tablicy obiektów?
Pratik Gaikwad
2
Uwaga dodatkowa: od wersji 1.7 (oczywiście nowszej niż twoja odpowiedź) maszynopis udostępnia dekoratory klas / właściwości, które pozwalają na napisanie czwartego rozwiązania w bardziej uporządkowany sposób.
GameAlchemist
1
Najlepsza dokumentacja, jaką znalazłem, to odpowiedź StackOverflow: stackoverflow.com/a/29837695/856501 . Użyłem dekoratorów w moim projekcie i chociaż chciałbym kilka innych funkcji, muszę powiedzieć, że działają jak urok.
GameAlchemist
2
Nie skoczyłbym jeszcze na dekoratorów do projektu produkcyjnego - pamiętaj, że wciąż są eksperymentalną funkcją. Nie opierałbym kodu rzeczywistego na „eksperymentach”, ponieważ według nas mogą one zniknąć w następnej wersji, a ty będziesz musiał przepisać sporo kodu lub utknąć na zawsze w starej wersji TS. Tylko moje 0,02 $
RVP
35

możesz użyć Object.assignNie wiem, kiedy to zostało dodane, obecnie używam Typescript 2.0.2, i wydaje się, że jest to funkcja ES6.

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

tutaj jest HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

oto, co mówi chrom

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

więc widać, że nie wykonuje rekurencyjnego przypisania

ksenoterracid
źródło
2
tak, w zasadzie, to w ten sposób: Object.assign. Dlaczego zatem mamy dwie odpowiedzi podobne do leksykonu nad tą?
phil294,
18
@Blauhim Ponieważ Object.assignnie będzie działać rekurencyjnie i nie będzie tworzył poprawnych typów obiektów, pozostawiając wartości jako Objectinstancje. Chociaż jest to przydatne w przypadku trywialnych zadań, serializacja złożonego typu nie jest z nim możliwa. Na przykład, jeśli właściwość klasy ma niestandardowy typ klasy, JSON.parse+ Object.assignutworzy instancję tej właściwości do Object. Skutki uboczne obejmują brakujące metody i akcesoria.
John Weisz
@JohnWeisz, klasa najwyższego poziomu przypisywania obiektów ma poprawny typ, a ja wspomniałem o rekurencyjnej rzeczy w tym ... to powiedziawszy, YMMV, i mogą to być łamacze transakcji.
ksenoterrakid
Cytowany bezpośrednio z pytania: „klasa ma członków, którzy są listami obiektów i członków, które są klasami, a te klasy mają członków, które są listami i / lub klasami [...] Wolałbym podejście, które sprawdza członka nadaje im nazwy i przypisuje je, tworząc listy i tworząc instancje w razie potrzeby, więc nie muszę pisać jawnego kodu dla każdego członka w każdej klasie - co nie ma miejsca Object.assign, gdzie nadal sprowadza się do pisania zagnieżdżonej instancji przez dłoń. Takie podejście jest odpowiednie dla bardzo prostych obiektów na poziomie samouczka, ale nie do rzeczywistego użytku.
John Weisz
@JohnWeisz na pewno, w większości odpowiedział na to, ponieważ nie było żadnych odpowiedzi i wydawało się proste w niektórych przypadkach użycia. Jestem pewien, że można go również użyć w połączeniu z innymi odpowiedziami, takimi jak odbicie, aby zrobić to, czego szukasz. Napisałem go również częściowo, aby później go zapamiętać. Patrząc na te odpowiedzi i po użyciu i napisaniu znacznie potężniejszych bibliotek, wydaje się, że nic nie jest dostępne do „prawdziwego użytku”.
ksenoterrakid
34

TLDR: TypedJSON (działający dowód koncepcji)


Podstawą złożoności tego problemu jest konieczność deserializacji JSON w czasie wykonywania przy użyciu informacji o typie, które istnieją tylko w czasie kompilacji . Wymaga to udostępnienia informacji o typie w czasie wykonywania.

Na szczęście można to rozwiązać w bardzo elegancki i solidny sposób za pomocą dekoratorów i ReflectDecorators :

  1. Użyj dekoratorów właściwości w przypadku właściwości podlegających serializacji, aby rejestrować informacje o metadanych i przechowywać je gdzieś, na przykład na prototypie klasy
  2. Podaj te informacje o metadanych do rekurencyjnego inicjalizatora (deserializatora)

 

Informacje o typie nagrywania

Dzięki kombinacji ReflectDecorators i dekoratorów właściwości można łatwo zapisać informacje o typie dotyczące właściwości. Podstawowym wdrożeniem tego podejścia byłoby:

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

Dla dowolnej właściwości powyższy fragment doda odniesienie do funkcji konstruktora właściwości do __propertyTypes__właściwości ukrytej w prototypie klasy. Na przykład:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

I to wszystko, w czasie wykonywania mamy wymagane informacje o typie, które można teraz przetwarzać.

 

Przetwarzanie informacji o typie

Najpierw musimy uzyskać Objectinstancję za pomocą JSON.parse- po tym możemy iterować po wejściach __propertyTypes__(zebrane powyżej) i odpowiednio utworzyć wymagane właściwości. Należy określić typ obiektu głównego, aby deserializator miał punkt początkowy.

Ponownie prosta implementacja tego podejścia byłaby:

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

Powyższy pomysł ma dużą zaletę deserializacji według typów oczekiwanych (dla wartości złożonych / obiektowych), zamiast tego, co jest obecne w JSON. Jeśli Personspodziewany jest a Person, to tworzona jest instancja. Z pewnych dodatkowych środków bezpieczeństwa w miejscu dla prymitywnych typów i tablic, takie podejście może być bezpieczne, która jest odporna na dowolny złośliwy JSON.

 

Edge Cases

Jeśli jednak cieszysz się teraz, że rozwiązanie jest tak proste, mam złe wieści: istnieje ogromna liczba przypadkowych spraw, którymi należy się zająć. Tylko niektóre z nich to:

  • Tablice i elementy tablic (szczególnie w tablicach zagnieżdżonych)
  • Wielopostaciowość
  • Klasy abstrakcyjne i interfejsy
  • ...

Jeśli nie chcesz się nimi bawić (założę się, że tego nie robisz), chętnie polecę działającą eksperymentalną wersję proof-of-concept wykorzystującą to podejście, TypedJSON - którą stworzyłem aby rozwiązać dokładnie ten problem, problem, z którym codziennie się spotykam.

Ze względu na to, że dekoratorzy wciąż są uważani za eksperymentalne, nie polecałbym używania go do celów produkcyjnych, ale jak dotąd dobrze mi to służyło.

John Weisz
źródło
TypedJSON działał świetnie; wielkie dzięki za referencje.
Neil,
Świetna robota, wymyśliłeś bardzo eleganckie rozwiązanie problemu, który od dłuższego czasu mnie niepokoi. Będę bardzo uważnie śledzić twój projekt!
John Strickler,
12

Używam tego faceta, aby wykonać zadanie: https://github.com/weichx/cerialize

To bardzo proste, ale potężne. To wspiera:

  • Serializacja i deserializacja całego drzewa obiektów.
  • Trwałe i przejściowe właściwości tego samego obiektu.
  • Haki do dostosowania logiki (de) serializacji.
  • Może (de) serializować w istniejącą instancję (świetną dla Angulara) lub generować nowe instancje.
  • itp.

Przykład:

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);
André
źródło
6

Stworzyłem narzędzie, które generuje interfejsy TypeScript i „mapę typów” środowiska wykonawczego do przeprowadzania sprawdzania typu środowiska wykonawczego w porównaniu z wynikami JSON.parse: ts.quicktype.io

Na przykład, biorąc pod uwagę ten JSON:

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

quicktype tworzy następujący interfejs TypeScript i mapę typów:

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

Następnie sprawdzamy wynik JSON.parsena podstawie mapy typów:

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

Pominąłem trochę kodu, ale możesz wypróbować quicktype, aby uzyskać szczegółowe informacje.

David Siegel
źródło
1
Po wielu godzinach badań i wypróbowaniu kilku technik parsowania mogę powiedzieć, że jest to doskonałe rozwiązanie - głównie dlatego, że dekoratorzy wciąż eksperymentują. * Oryginalny link jest dla mnie uszkodzony; ale ts.quicktype.io działa. * Konwersja JSON do schematu JSON to dobry pierwszy krok.
LexieHankins,
3

Opcja # 5: Korzystanie z konstruktorów maszynopisu i jQuery.extend

Wydaje się, że jest to najbardziej konserwowalna metoda: dodaj konstruktor, który przyjmuje jako parametr strukturę json i rozszerz obiekt json. W ten sposób możesz przeanalizować strukturę json w całym modelu aplikacji.

Nie ma potrzeby tworzenia interfejsów ani wyświetlania właściwości w konstruktorze.

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

W wywołaniu zwrotnym ajax, w którym otrzymujesz firmę do obliczania wynagrodzeń:

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}
Anthony Brenelière
źródło
skąd $.extendpochodzą?
whale_steward
@whale_steward Zakładam, że autor odnosi się do biblioteki jQuery. W świecie JavaScript „$” to bardzo często ktoś korzystający z jQuery.
Nick Roth,
jak go zaimportować? wystarczy dołączyć go do html head wystarczy?
whale_steward
tak, aktualizuję odpowiedź, aby zastąpić $ przez jQuery. zaimportuj plik jQuery.js do nagłówka html, a następnie zainstaluj i dodaj @ types / jquery w sekcji package.json, devDependencies.
Anthony Brenelière
1
Zauważ, że w Javascript powinieneś to zrobić Object.assign, co usuwa tę zależność od jQuery.
Léon Pelletier
2

W przypadku prostych obiektów podoba mi się ta metoda:

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

Wykorzystanie możliwości definiowania właściwości w konstruktorze pozwala na zwięzłość.

To daje ci wpisany obiekt (w porównaniu do wszystkich odpowiedzi, które używają Object.assign lub jakiegoś wariantu, który daje ci Object) i nie wymaga zewnętrznych bibliotek ani dekoratorów.

Stevex
źródło
1

Czwarta opcja opisana powyżej to prosty i przyjemny sposób, aby to zrobić, którą należy połączyć z drugą opcją w przypadku, gdy trzeba obsłużyć hierarchię klas, na przykład listę członków, która jest jednym z przypadków podklasy superklasa Członka, np. Dyrektor przedłuża Członka lub Student przedłuża Członka. W takim przypadku musisz podać typ podklasy w formacie json

Xavier Méhaut
źródło
1

JQuery .extend robi to za Ciebie:

var mytsobject = new mytsobject();

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

$.extend(mytsobject, newObj); //mytsobject will now contain a & b
Daniel
źródło
1

najlepsze, co znalazłem w tym celu, to transformator klasy. github.com/typestack/class-transformer

Tak to wykorzystujesz:

Niektóre klasy:

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

Jeśli użyjesz dekoratora @Type, zostaną również utworzone zagnieżdżone właściwości.

Fabianus
źródło
0

Może nie rzeczywiste, ale proste rozwiązanie:

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

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

pracuj też dla trudnych zależności !!!

Михайло Пилип
źródło
9
To podejście nie działa zgodnie z oczekiwaniami. Jeśli sprawdzisz wyniki środowiska wykonawczego, bazbędzie to typ, Objecta nie typ Bar.Działa w tym prostym przypadku, ponieważ Barnie ma żadnych metod (tylko prymitywne właściwości). Gdyby Baristniała metoda podobna isEnabled(), to podejście nie udałoby się, ponieważ metoda ta nie znalazłaby się w serializowanym ciągu JSON.
Todd
0

Inna opcja z wykorzystaniem fabryk

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

używać w ten sposób

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. sprawia, że ​​twoje zajęcia są proste
  2. wtrysk dostępny dla fabryk dla elastyczności
Anthony Johnston
źródło
0

Osobiście wolę opcję nr 3 @Ingo Bürk. Ulepszyłem jego kody, aby obsługiwały tablicę złożonych danych i tablicę prymitywnych danych.

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
alireza etemadi
źródło
-1

możesz zrobić jak poniżej

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

i

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
Pani Ayub Ali Sarker
źródło
W rzeczywistości nie spowoduje to wystąpienia środowiska wykonawczego oczekiwanego typu obiektu. Wygląda na to, że działa, gdy typ ma tylko podstawowe właściwości, ale zawiedzie, gdy typ ma metody. Definicje interfejsów również nie są dostępne w czasie wykonywania (tylko czas kompilacji).
Todd
-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;
    }
}
użytkownik8390810
źródło
nazwij treść jak poniżej:
user8390810
<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810
Nie wydaje się to „[tworzyć instancji] klas w razie potrzeby”.
LexieHankins