Wie initialisiere ich ein TypeScript-Objekt mit einem JSON-Objekt?

197

Ich erhalte ein JSON-Objekt von einem AJAX-Aufruf an einen REST-Server. Dieses Objekt verfügt über Eigenschaftsnamen, die meiner TypeScript-Klasse entsprechen (dies ist eine Fortsetzung dieser Frage ).

Was ist der beste Weg, um es zu initialisieren? Ich denke nicht, dass dies funktionieren wird, da die Klasse (& JSON-Objekt) Mitglieder hat, die Listen von Objekten und Mitglieder sind, die Klassen sind, und diese Klassen Mitglieder haben, die Listen und / oder Klassen sind.

Aber ich würde einen Ansatz bevorzugen, bei dem die Mitgliedsnamen nachgeschlagen und zugewiesen werden, Listen erstellt und Klassen nach Bedarf instanziiert werden, sodass ich nicht für jedes Mitglied in jeder Klasse expliziten Code schreiben muss (es gibt eine Menge!).

David Thielen
quelle
1
Warum haben Sie dies erneut gefragt (da die Antwort, die ich in der anderen Frage gegeben habe, besagte, dass dies nicht funktionieren würde und es darum ging, Eigenschaften in ein vorhandenes Objekt zu kopieren)?
WiredPrairie
1
Mögliches Duplikat von Wie kann ich ein JSON-Objekt in eine Typoskriptklasse
umwandeln?
3
@WiredPrairie Diese Frage ist anders. Es wird gefragt, ob ich die Eigenschaften einzeln durchgehen und sie zuweisen kann. Die anderen Fragen waren, ob ich es besetzen könnte.
David Thielen
1
@WiredPrairie cont: Wenn Sie so lange in Eigenschaften eintauchen, bis Sie nur noch die primitiven Typen erreicht haben, können diese übergreifend zugewiesen werden.
David Thielen
2
Es werden immer noch alle Werte kopiert, so wie ich es vorgeschlagen habe. In TypeScript gibt es keine neue Möglichkeit, dies zu tun, da es sich um ein grundlegendes Design von JavaScript handelt. Bei großen Objekten möchten Sie möglicherweise keine Werte kopieren und stattdessen nur auf die Datenstruktur "einwirken".
WiredPrairie

Antworten:

188

Dies sind einige kurze Aufnahmen, um einige verschiedene Möglichkeiten aufzuzeigen. Sie sind keineswegs "vollständig" und als Haftungsausschluss halte ich es nicht für eine gute Idee, dies so zu tun. Außerdem ist der Code nicht zu sauber, da ich ihn nur ziemlich schnell zusammen geschrieben habe.

Auch als Hinweis: Natürlich müssen deserialisierbare Klassen Standardkonstruktoren haben, wie dies in allen anderen Sprachen der Fall ist, in denen mir Deserialisierung jeglicher Art bekannt ist. Natürlich wird sich Javascript nicht beschweren, wenn Sie einen nicht standardmäßigen Konstruktor ohne Argumente aufrufen, aber die Klasse sollte dann besser darauf vorbereitet sein (außerdem wäre es nicht wirklich der "Typenskript-Weg").

Option 1: Überhaupt keine Laufzeitinformationen

Das Problem bei diesem Ansatz besteht hauptsächlich darin, dass der Name eines Mitglieds mit seiner Klasse übereinstimmen muss. Dies beschränkt Sie automatisch auf ein Mitglied desselben Typs pro Klasse und verstößt gegen mehrere Regeln für bewährte Verfahren. Ich rate dringend davon ab, aber liste es einfach hier auf, weil es der erste "Entwurf" war, als ich diese Antwort schrieb (weshalb die Namen auch "Foo" usw. sind).

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

Option 2: Der Name Eigenschaft

Um das Problem in Option 1 zu beseitigen, benötigen wir Informationen darüber, welcher Typ ein Knoten im JSON-Objekt ist. Das Problem ist, dass diese Dinge in Typescript Konstrukte zur Kompilierungszeit sind und wir sie zur Laufzeit benötigen - aber Laufzeitobjekte kennen ihre Eigenschaften erst, wenn sie festgelegt sind.

Eine Möglichkeit besteht darin, die Klassen auf ihre Namen aufmerksam zu machen. Sie benötigen diese Eigenschaft jedoch auch im JSON. Eigentlich brauchst du es nur im 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);

Option 3: Explizite Angabe der Elementtypen

Wie oben erwähnt, sind die Typinformationen von Klassenmitgliedern zur Laufzeit nicht verfügbar - es sei denn, wir stellen sie zur Verfügung. Wir müssen dies nur für nicht-primitive Mitglieder tun und es kann losgehen:

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

Option 4: Der ausführliche, aber ordentliche Weg

Update 01/03/2016: Wie @GameAlchemist in den Kommentaren betonte ( Idee , Implementierung ) ab Typescript 1.7 , kann die unten beschriebene Lösung mithilfe von Klassen- / Eigenschaftsdekoratoren besser geschrieben werden.

Serialisierung ist immer ein Problem und meiner Meinung nach ist der beste Weg ein Weg, der einfach nicht der kürzeste ist. Von allen Optionen würde ich dies bevorzugen, da der Autor der Klasse die volle Kontrolle über den Status deserialisierter Objekte hat. Wenn ich raten müsste, würde ich sagen, dass alle anderen Optionen früher oder später Sie in Schwierigkeiten bringen werden (es sei denn, Javascript bietet eine native Methode, um damit umzugehen).

Das folgende Beispiel wird der Flexibilität nicht gerecht. Es kopiert wirklich nur die Struktur der Klasse. Der Unterschied, den Sie hier beachten müssen, besteht darin, dass die Klasse die volle Kontrolle über die Verwendung jeder Art von JSON hat, die den Status der gesamten Klasse steuern soll (Sie können Dinge berechnen usw.).

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
quelle
12
Option 4 ist ein vernünftiger Weg. Sie müssen den Deserialisierungscode noch schreiben, aber er gehört zur selben Klasse und ist vollständig steuerbar. Wenn Sie aus Java kommen, ist dies vergleichbar mit dem Schreiben equalsoder toStringMethoden (nur dass Sie diese normalerweise automatisch generieren lassen). Es sollte nicht zu schwierig sein, einen Generator zu schreiben, deserializewenn Sie möchten, aber es kann einfach keine Laufzeitautomatisierung sein.
Ingo Bürk
2
@ IngoBürk, ich weiß, dass ich diese Frage 2 Jahre später stelle, aber wie funktioniert das bei einer Reihe von Objekten? Der obige Beispielcode funktioniert gut für JSON-Objekte. Wie kann es für ein Array von Objekten verwendet werden?
Pratik Gaikwad
2
Eine Randbemerkung: Seit dem 1.7 (zugegebenermaßen neuer als Ihre Antwort) bietet das Typoskript Klassen- / Eigenschaftsdekoratoren, mit denen die 4. Lösung ordentlicher geschrieben werden kann.
GameAlchemist
1
Die beste Dokumentation, die ich gefunden habe, ist eine StackOverflow-Antwort: stackoverflow.com/a/29837695/856501 . Ich habe in einem meiner Projekte Dekorateure verwendet, und obwohl ich ein paar andere Funktionen möchte, muss ich sagen, dass sie wie ein Zauber wirken.
GameAlchemist
2
Ich würde noch nicht auf Dekorateure für ein Produktionsprojekt setzen - denken Sie daran, dass sie immer noch ein experimentelles Feature sind. Ich würde realen Code nicht auf "Experimenten" basieren, da sie für uns möglicherweise in der nächsten Version verschwunden sind und Sie eine Menge Code neu schreiben müssten oder für immer an einer alten TS-Version festhalten müssten. Nur meine $ .02
RVP
35

Sie können verwenden Object.assignIch weiß nicht, wann dies hinzugefügt wurde, ich verwende derzeit Typescript 2.0.2, und dies scheint eine ES6-Funktion zu sein.

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

hier ist HalJson

export class HalJson {
    _links: HalLinks;
}

export class HalLinks implements Links {
}

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

export interface Link {
    readonly href: URL;
}

Hier ist, was Chrom sagt, dass es ist

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

Sie können also sehen, dass die Zuweisung nicht rekursiv erfolgt

Xenoterracid
quelle
2
Im Grunde ist es das : Object.assign. Warum haben wir dann zwei lexikonartige Antworten über dieser?
Phil294
18
@Blauhim Weil Object.assignnicht rekursiv funktioniert und keine korrekten Objekttypen instanziiert werden, wobei Werte als ObjectInstanzen verbleiben . Während es für triviale Aufgaben in Ordnung ist, ist damit keine komplexe Serialisierung möglich. Wenn eine Klasseneigenschaft beispielsweise einen benutzerdefinierten Klassentyp hat, instanziiert JSON.parse+ Object.assigndiese Eigenschaft auf Object. Zu den Nebenwirkungen zählen fehlende Methoden und Zugriffsmethoden.
John Weisz
@ JohnWeisz Die Klasse der Objektzuweisung der obersten Ebene hat den richtigen Typ, und ich erwähnte die rekursive Sache in diesem ... das heißt, YMMV, und das könnten Deal Breaker sein.
Xenoterracide
Direkt aus der Frage zitiert: "Die Klasse hat Mitglieder, die Listen von Objekten und Mitglieder sind, die Klassen sind, und diese Klassen haben Mitglieder, die Listen und / oder Klassen sind. [...] Ich würde einen Ansatz bevorzugen, der das Mitglied nachschlägt benennt und weist sie zu, erstellt Listen und instanziiert Klassen nach Bedarf, sodass ich nicht für jedes Mitglied in jeder Klasse expliziten Code schreiben muss " - was nicht der Fall ist Object.assign, wenn es immer noch darauf ankommt, verschachtelte Instanziierungen von zu schreiben Hand. Dieser Ansatz eignet sich für sehr einfache Objekte auf Tutorial-Ebene, jedoch nicht für den tatsächlichen Gebrauch.
John Weisz
@ JohnWeisz sicher, meistens mit diesem beantwortet, weil es keine Antworten gab und für einige Anwendungsfälle einfach schien. Ich bin sicher, dass es auch in Kombination mit anderen Antworten wie Reflexion verwendet werden kann, um das zu tun, wonach Sie suchen. Ich habe es auch teilweise geschrieben, damit ich mich später daran erinnere. Wenn man sich diese Antworten ansieht und viel leistungsfähigere Bibliotheken verwendet und geschrieben hat, scheint nichts für den "echten Gebrauch" verfügbar zu sein.
Xenoterracide
34

TLDR: TypedJSON (funktionierender Proof of Concept)


Die Wurzel der Komplexität dieses Problems liegt darin, dass JSON zur Laufzeit mithilfe von Typinformationen deserialisiert werden muss , die nur zur Kompilierungszeit vorhanden sind . Dies erfordert, dass Typinformationen zur Laufzeit irgendwie verfügbar gemacht werden.

Glücklicherweise kann dies mit Dekorateuren und ReflectDecorators auf sehr elegante und robuste Weise gelöst werden :

  1. Verwenden Sie Eigenschaftsdekoratoren für Eigenschaften, die einer Serialisierung unterliegen, um Metadateninformationen aufzuzeichnen und diese Informationen irgendwo zu speichern, z. B. auf dem Klassenprototyp
  2. Geben Sie diese Metadateninformationen an einen rekursiven Initialisierer (Deserializer) weiter.

 

Typinformationen aufzeichnen

Mit einer Kombination aus ReflectDecorators und Eigenschaftsdekoratoren können Typinformationen zu einer Eigenschaft einfach aufgezeichnet werden. Eine rudimentäre Umsetzung dieses Ansatzes wäre:

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

Für eine bestimmte Eigenschaft fügt das obige Snippet der versteckten __propertyTypes__Eigenschaft des Klassenprototyps einen Verweis auf die Konstruktorfunktion der Eigenschaft hinzu . Beispielsweise:

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

    @JsonMember// Number
    level: number;
}

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

    @JsonMember// Language
    language: Language;
}

Und das war's, wir haben zur Laufzeit die erforderlichen Typinformationen, die jetzt verarbeitet werden können.

 

Typinformationen verarbeiten

Wir müssen zuerst eine ObjectInstanz mit erhalten JSON.parse- danach können wir über die gesamten Einträge in __propertyTypes__(oben gesammelt) iterieren und die erforderlichen Eigenschaften entsprechend instanziieren. Der Typ des Stammobjekts muss angegeben werden, damit der Deserializer einen Ausgangspunkt hat.

Wiederum wäre eine absolut einfache Implementierung dieses Ansatzes:

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

Die obige Idee hat den großen Vorteil, dass sie nach erwarteten Typen (für komplexe / Objektwerte) deserialisiert wird , anstatt nach dem, was im JSON vorhanden ist. Wenn a Personerwartet wird, wird eine PersonInstanz erstellt. Mit einigen zusätzlichen Sicherheitsmaßnahmen für primitive Typen und Arrays kann dieser Ansatz sicher gemacht werden, der jedem böswilligen JSON widersteht .

 

Randfälle

Wenn Sie jetzt aber freuen uns , dass die Lösung ist , dass einfach, habe er eine schlechte Nachricht: Es ist eine große Anzahl von Grenzfällen , die betreut werden müssen. Nur einige davon sind:

  • Arrays und Array-Elemente (insbesondere in verschachtelten Arrays)
  • Polymorphismus
  • Abstrakte Klassen und Schnittstellen
  • ...

Wenn Sie nicht mit all diesen Dingen herumspielen möchten (ich wette, Sie tun es nicht), würde ich Ihnen gerne eine funktionierende experimentelle Version eines Proof-of-Concept empfehlen, der diesen Ansatz verwendet, TypedJSON - den ich erstellt habe Um genau dieses Problem anzugehen, stelle ich mich täglich.

Aufgrund der Tatsache, dass Dekorateure immer noch als experimentell angesehen werden, würde ich nicht empfehlen, es für Produktionszwecke zu verwenden, aber bisher hat es mir gute Dienste geleistet.

John Weisz
quelle
TypedJSON hat großartig funktioniert; Vielen Dank für die Referenz.
Neil
Tolle Arbeit, Sie haben eine sehr elegante Lösung für ein Problem gefunden, das mich schon seit einiger Zeit beschäftigt. Ich werde Ihr Projekt sehr genau verfolgen!
John Strickler
12

Ich habe diesen Typen benutzt, um die Arbeit zu erledigen: https://github.com/weichx/cerialize

Es ist sehr einfach und doch mächtig. Es unterstützt:

  • Serialisierung und Deserialisierung eines ganzen Objektbaums.
  • Persistente und vorübergehende Eigenschaften für dasselbe Objekt.
  • Hooks zum Anpassen der (De-) Serialisierungslogik.
  • Es kann in eine vorhandene Instanz (de) serialisiert werden (ideal für Angular) oder neue Instanzen generieren.
  • etc.

Beispiel:

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é
quelle
6

Ich habe ein Tool erstellt, das TypeScript-Schnittstellen und eine Laufzeit- "Typzuordnung" generiert, um eine Laufzeit-Typprüfung anhand der Ergebnisse von JSON.parse: ts.quicktype.io durchzuführen

Beispiel: JSON:

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

quicktype erzeugt die folgende TypeScript-Oberfläche und Type Map:

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

Dann überprüfen wir das Ergebnis von anhand JSON.parseder Typenkarte:

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

Ich habe einen Code weggelassen , aber Sie können versuchen, die Details mit Quicktype zu überprüfen .

David Siegel
quelle
1
Nach vielen Stunden Recherche und einigen Parsing-Techniken kann ich sagen, dass dies eine hervorragende Lösung ist - vor allem, weil Dekorateure noch experimentieren. * Der ursprüngliche Link ist für mich defekt; aber ts.quicktype.io funktioniert. * Das Konvertieren von JSON in JSON-Schema ist ein guter erster Schritt.
LexieHankins
3

Option 5: Verwenden von Typescript-Konstruktoren und jQuery.extend

Dies scheint die am besten zu wartende Methode zu sein: Fügen Sie einen Konstruktor hinzu, der die JSON-Struktur als Parameter verwendet, und erweitern Sie das JSON-Objekt. Auf diese Weise können Sie eine JSON-Struktur in das gesamte Anwendungsmodell analysieren.

Es ist nicht erforderlich, Schnittstellen zu erstellen oder Eigenschaften im Konstruktor aufzulisten.

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

In Ihrem Ajax-Rückruf, in dem Sie ein Unternehmen zur Berechnung der Gehälter erhalten:

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

   // call the methods on your newCompany object ...
   newCompany.calculateSalaries()
}
Anthony Brenelière
quelle
woher $.extendkommen
whale_steward
@whale_steward Ich würde annehmen, dass der Autor auf die jQuery-Bibliothek verweist. In der JavaScript-Welt ist '$' sehr oft jemand, der jQuery verwendet.
Nick Roth
Wie importiere ich es? nur auf HTML-Kopf einfügen ist genug?
whale_steward
Ja, ich aktualisiere die Antwort, um $ durch jQuery zu ersetzen. Importieren Sie jQuery.js in den HTML-Kopf und installieren und fügen Sie @ types / jquery in Ihrem Abschnitt package.json, devDependencies hinzu.
Anthony Brenelière
1
Beachten Sie, dass Sie dies in Javascript tun sollten Object.assign, wodurch diese Abhängigkeit von jQuery beseitigt wird.
Léon Pelletier
2

Für einfache Objekte mag ich diese Methode:

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

Durch die Nutzung der Fähigkeit, Eigenschaften im Konstruktor zu definieren, kann dieser präzise gestaltet werden.

Dadurch erhalten Sie ein typisiertes Objekt (im Vergleich zu allen Antworten, die Object.assign oder eine Variante verwenden, die Ihnen ein Objekt gibt) und benötigen keine externen Bibliotheken oder Dekoratoren.

stevex
quelle
1

Die oben beschriebene 4. Option ist eine einfache und nette Möglichkeit, die mit der 2. Option kombiniert werden muss, wenn Sie eine Klassenhierarchie wie beispielsweise eine Mitgliederliste behandeln müssen, bei der es sich um Vorkommen von Unterklassen von handelt Ein Mitglied der Superklasse, z. B. Direktor erweitert Mitglied oder Schüler erweitert Mitglied. In diesem Fall müssen Sie den Unterklassentyp im JSON-Format angeben

Xavier Méhaut
quelle
1

JQuery .extend erledigt dies für Sie:

var mytsobject = new mytsobject();

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

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

Vielleicht keine tatsächliche, aber einfache Lösung:

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

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

arbeite auch für schwierige Abhängigkeiten !!!

Михайло Пилип
quelle
9
Dieser Ansatz funktioniert nicht wie erwartet. Wenn Sie die Laufzeitergebnisse überprüfen, bazsind sie vom Typ Objectund nicht vom Typ. Bar.Dies funktioniert in diesem einfachen Fall, da Barkeine Methoden (nur primitive Eigenschaften) vorhanden sind. Wenn Bareine Methode wie isEnabled()diese vorhanden wäre, würde dieser Ansatz fehlschlagen, da diese Methode nicht in der serialisierten JSON-Zeichenfolge enthalten wäre.
Todd
0

Eine weitere Option mit Fabriken

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

benutze so

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. hält deine Klassen einfach
  2. Injektion zur Verfügung für die Fabriken für Flexibilität
Anthony Johnston
quelle
0

Das Beste, was ich für diesen Zweck gefunden habe, ist der Klassentransformator. github.com/typestack/class-transformer

So benutzt du es:

Einige Klasse:

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

Wenn Sie den @ Type Decorator verwenden, werden auch verschachtelte Eigenschaften erstellt.

Fabianus
quelle
0

Ich persönlich bevorzuge Option 3 von @Ingo Bürk. Und ich habe seine Codes verbessert, um ein Array komplexer Daten und ein Array primitiver Daten zu unterstützen.

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
quelle
-1

Sie können wie unten tun

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

und

var instance: Instance = <Instance>({
      id: null,
      name: '',
      type: ''
    });
Md Ayub Ali Sarker
quelle
Dadurch wird keine Laufzeitinstanz Ihres erwarteten Objekttyps erstellt. Es scheint zu funktionieren, wenn Ihr Typ nur primitive Eigenschaften hat, schlägt jedoch fehl, wenn ein Typ Methoden hat. Schnittstellendefinitionen sind zur Laufzeit ebenfalls nicht verfügbar (nur Erstellungszeit).
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;
    }
}
user8390810
quelle
Rufen Sie den Inhalt wie im folgenden Beispiel auf:
user8390810
<a class="navbar-brand" href="#"> {{keyItems.key.logo}} </a>
user8390810
Dies scheint nicht "Klassen nach Bedarf zu [instanziieren]".
LexieHankins