642 Stimmen

Wie konvertiere ich ein JSON-Objekt in eine TypeScript-Klasse?

Ich lese ein JSON-Objekt von einem entfernten REST-Server. Dieses JSON-Objekt hat alle Eigenschaften einer TypeScript-Klasse (nach Design). Wie kann ich dieses empfangene JSON-Objekt in eine Typvariable umwandeln?

Ich möchte keine TypeScript-Variable auffüllen (d. h. einen Konstruktor haben, der dieses JSON-Objekt übernimmt). Es ist groß und das Kopieren jedes einzelnen Unterobjekts und jeder Eigenschaft würde viel Zeit in Anspruch nehmen.

Update: Sie können es jedoch in eine TypeScript-Schnittstelle umwandeln!

33voto

Flavien Volken Punkte 15992

Es gibt bisher keine Möglichkeit, automatisch zu überprüfen, ob das JSON-Objekt, das Sie vom Server erhalten haben, den erwarteten (also dem TypeScript-Interface entsprechenden) Eigenschaften entspricht. Aber Sie können Benutzerdefinierte Typ-Wächter verwenden

Unter Berücksichtigung des folgenden Interface und eines simplen JSON-Objekts (es hätte jeder Typ sein können):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

Drei mögliche Wege:

A. Typ-Assert oder einfacher statischer Cast nach der Variablen

const myObject: MyInterface = json as MyInterface;

B. Einfacher statischer Cast, vor der Variablen und zwischen Diamanten

const myObject: MyInterface = json;

C. Fortgeschrittener dynamischer Cast, Sie überprüfen selbst die Struktur des Objekts

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

Sie können mit diesem Beispiel hier spielen%20%7B%0A%20%20%20%20%2F%2F%20no%20error%2C%20the%20type%20is%20inferred%20by%20our%20custom%20type%20guard%0A%20%20%20%20%2F%2F%20in%20short%2C%20we%20are%20sure%20json%20conforms%20to%20MyInterface%20at%20this%20point%0A%20%20%20%20json.key%3B%0A%7D%0Aelse%20%7B%0A%20%20%20%20%2F%2F%20Fallback%20if%20the%20dynamic%20cast%20did%20not%20work%0A%20%20%20%20throw%20new%20Error(%60Expected%20MyInterface%2C%20got%20'%24%7Bjson%7D'.%60)%3B%0A%7D%0A)

Es ist zu beachten, dass die Schwierigkeit hier darin besteht, die isMyInterface Funktion zu schreiben. Ich hoffe, dass TS früher oder später einen Dekorator hinzufügt, um komplexe Typisierungen zur Laufzeit zu exportieren und dem Laufzeit die Überprüfung der Struktur des Objekts zu überlassen, wenn nötig. Derzeit könnten Sie entweder einen JSON-Schema-Validator verwenden, der ungefähr dasselbe Ziel verfolgt, ODER diesen Generator für Funktionen zur Laufzeittypüberprüfung

30voto

Timothy Perez Punkte 19724

Zusammenfassung: Eine Zeile

// Dies setzt voraus, dass Ihre Konstruktormethode Eigenschaften aus dem Argument zuweist.
.map((instanceData: MyClass) => new MyClass(instanceData));

Die Ausführliche Antwort

Ich würde die Object.assign-Methode nicht empfehlen, da sie Ihren Klasseninstanz unangemessen mit irrelevanten Eigenschaften (sowie definierten Closures) versehen kann, die nicht innerhalb der Klasse selbst deklariert wurden.

In der Klasse, in die Sie deserialisieren möchten, sollten Sie sicherstellen, dass alle zu deserialisierenden Eigenschaften definiert sind (null, leeres Array, etc). Indem Sie Ihre Eigenschaften mit Anfangswerten definieren, machen Sie ihre Sichtbarkeit beim Versuch, Klassenmember zu iterieren und Werte zuzuweisen, sichtbar (siehe Methode deserialize unten).

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Beachten Sie, dass this.active nicht in keys aufgeführt wird, da es deklariert, aber nicht definiert ist
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

In dem obigen Beispiel habe ich einfach eine deserialize-Methode erstellt. In einem echten Beispiel würde ich sie in einer wiederverwendbaren Basisklasse oder Servicemethode zentralisiert haben.

So verwenden Sie dies beispielsweise in einer http-Antwort...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

Wenn tslint/ide sich über den nicht kompatiblen Argumenttyp beschwert, wandeln Sie das Argument einfach in denselben Typ um, indem Sie spitze Klammern verwenden, Beispiel:

const person = new Person( { name: 'John', age: 35, id: 1 });

Wenn Sie Klassenmember haben, die von einem bestimmten Typ sind (aka: Instanz einer anderen Klasse), können Sie sie über Getter/Setter-Methoden in typisierte Instanzen umwandeln.

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // Konstruktor- & Deserialisierungsmethoden...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

Das obige Beispiel wird sowohl acct als auch die Liste der tasks in ihre jeweiligen Klasseninstanzen deserialisieren.

19voto

Anthony Brenelière Punkte 54324

Angenommen, das JSON hat die gleichen Eigenschaften wie Ihre TypeScript-Klasse, dann müssen Sie Ihre JSON-Eigenschaften nicht auf Ihr TypeScript-Objekt kopieren. Sie müssen lediglich Ihr TypeScript-Objekt konstruieren und die JSON-Daten im Konstruktor übergeben.

In Ihrem Ajax-Callback erhalten Sie ein Unternehmen:

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

   // die Methoden auf Ihrem neuen Unternehmen aufrufen ...
}

Um das zu erreichen:

1) Fügen Sie einen Konstruktor in Ihrer TypeScript-Klasse hinzu, der die JSON-Daten als Parameter akzeptiert. In diesem Konstruktor erweitern Sie Ihr JSON-Objekt mit jQuery, so wie hier: $.extend( this, jsonData). $.extend erlaubt das Beibehalten der JavaScript-Prototypen und fügt die Eigenschaften des JSON-Objekts hinzu.

2) Beachten Sie, dass Sie das Gleiche für verknüpfte Objekte tun müssen. Im Fall von Mitarbeitern im Beispiel erstellen Sie ebenfalls einen Konstruktor, der den Teil der JSON-Daten für Mitarbeiter akzeptiert. Sie verwenden $.map, um JSON-Mitarbeiter in TypeScript-Mitarbeiterobjekte zu übersetzen.

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

export class Employee
{
    name: string;
    salary: number;

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);
    }
}

Dies ist die beste Lösung, die ich gefunden habe, wenn es um TypeScript-Klassen und JSON-Objekte geht.

17voto

Adam111p Punkte 2983

In meinem Fall funktioniert es. Ich habe die Funktionen Object.assign (target, sources ...) verwendet. Zuerst wird das richtige Objekt erstellt, dann werden die Daten vom JSON-Objekt auf das Ziel kopiert. Beispiel :

let u:User = new User();
Object.assign(u , jsonUsers);

Und ein fortgeschritteneres Beispiel für die Verwendung. Ein Beispiel mit der Verwendung des Arrays.

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("Benutzer: " + this.users[i].id);
    console.log("Benutzer-ID von der Funktion (testen, ob es funktioniert): " + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}

9voto

Neil Punkte 5834

Obwohl es sich nicht direkt um Casting handelt, habe ich https://github.com/JohnWhiteTB/TypedJSON als nützliche Alternative gefunden.

@JsonObject
class Person {
    @JsonMember
    firstName: string;

    @JsonMember
    lastName: string;

    public getFullname() {
        return this.firstName + " " + this.lastName;
    }
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);

person instanceof Person; // true
person.getFullname(); // "John Doe"

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X