web-dev-qa-db-de.com

Wie kann ich ein JSON-Objekt in eine TypeScript-Klasse umwandeln?

Ich habe ein JSON-Objekt von einem Remote-Server REST gelesen. Dieses JSON-Objekt verfügt über alle Eigenschaften einer TypeScript-Klasse (von Entwurf). Wie kann ich das empfangene JSON-Objekt in einen Typ var umwandeln?

Ich möchte keine TypeScript-Variable füllen (dh einen Konstruktor haben, der dieses JSON-Objekt verwendet). Es ist umfangreich, und das Kopieren von Objekten über Unterobjekte und Eigenschaften nach Eigenschaften würde viel Zeit in Anspruch nehmen.

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

273
David Thielen

Sie können nicht einfach ein einfaches altes JavaScript-Ergebnis aus einer Ajax-Anforderung in eine prototypische JavaScript/TypeScript-Klasseninstanz konvertieren. Dafür gibt es eine Reihe von Techniken, und im Allgemeinen müssen Daten kopiert werden. Wenn Sie keine Instanz der Klasse erstellen, hat sie keine Methoden oder Eigenschaften. Es bleibt ein einfaches JavaScript-Objekt. 

Wenn Sie sich nur mit Daten befassen, könnten Sie einfach eine Umwandlung in eine Schnittstelle vornehmen (da es sich lediglich um eine Struktur für die Kompilierzeit handelt), müssen Sie jedoch eine TypeScript-Klasse verwenden, die die Dateninstanz verwendet und Vorgänge mit diesen Daten ausführt.

Einige Beispiele zum Kopieren der Daten:

  1. Kopieren eines AJAX JSON-Objekts in ein vorhandenes Objekt
  2. Parse JSON-String in einem bestimmten Objektprototyp in JavaScript

Im Wesentlichen würden Sie nur:

var d = new MyRichObject();
d.copyInto(jsonResult);
115
WiredPrairie

Ich hatte das gleiche Problem und habe eine Bibliothek gefunden, die diese Aufgabe erfüllt: https://github.com/pleerock/class-transformer .

Es funktioniert so: 

        let jsonObject = response.json() as Object;
        let fooInstance = plainToClass(Models.Foo, jsonObject);
        return fooInstance;

Es unterstützt verschachtelte Kinder, aber Sie müssen das Mitglied Ihrer Klasse dekorieren.

62
Pak

In TypeScript können Sie eine Typ-Assertion durchführen, indem Sie eine Schnittstelle und generics verwenden:

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

Wo ILocationMap die Form Ihrer Daten beschreibt. Der Vorteil dieser Methode ist, dass Ihr JSON möglicherweise mehr Eigenschaften enthält, die Form jedoch die Bedingungen der Schnittstelle erfüllt.

Ich hoffe das hilft!

40
user756310

Ich habe einen sehr interessanten Artikel zum generischen Casting von JSON in eine TypeScript-Klasse gefunden:

http://cloudmark.github.io/Json-Mapping/

Sie erhalten folgenden Code:

let example = {
                "name": "Mark", 
                "surname": "Galea", 
                "age": 30, 
                "address": {
                  "first-line": "Some where", 
                  "second-line": "Over Here",
                  "city": "In This City"
                }
              };

MapUtils.deserialize(Person, example);  // custom class
24
Philip Miglinci

Wenn Sie ES6 verwenden, versuchen Sie Folgendes:

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {

  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

Dies funktioniert jedoch leider nicht beim Nestobjekt.

22
ilovezcd

Angenommen, der Json hat dieselben Eigenschaften wie Ihre TypeScript-Klasse, müssen Sie Ihre Json-Eigenschaften nicht in Ihr TypeScript-Objekt kopieren. Sie müssen nur Ihr TypeScript-Objekt erstellen, indem Sie die Json-Daten im Konstruktor übergeben.

In Ihrem Ajax Callback erhalten Sie eine Firma:

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

   // call the methods on your newCompany object ...
}

Um diese Arbeit zu machen:

1) Fügen Sie in Ihrer TypeScript-Klasse einen Konstruktor hinzu, der die Json-Daten als Parameter übernimmt. In diesem Konstruktor erweitern Sie Ihr json-Objekt mit jQuery wie folgt: $.extend( this, jsonData). $ .extend ermöglicht das Beibehalten der Javascript-Prototypen, während die Eigenschaften des Json-Objekts hinzugefügt werden.

2) Beachten Sie, dass Sie dasselbe für verknüpfte Objekte tun müssen. Bei den Mitarbeitern im Beispiel erstellen Sie auch einen Konstruktor, der den Teil der Json-Daten für die Mitarbeiter enthält. Sie rufen $ .map auf, um json-Mitarbeiter in TypeScript-Employee-Objekte 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 im Umgang mit TypeScript-Klassen und Json-Objekten gefunden habe.

17

In meinem Fall funktioniert es. Ich habe Funktionen verwendet Object.assign (Ziel, Quellen ...) . Zuerst wird das richtige Objekt erstellt und dann die Daten vom Json-Objekt zum Ziel kopiert. Beispiel:

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

Und ein fortgeschritteneres Anwendungsbeispiel. Ein Beispiel mit dem Array.

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("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

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

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

TLDR: Ein Liner

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

Die ausführliche Antwort

Ich würdenotden Object.assign-Ansatz empfehlen, da er Ihre Klasseninstanz unangemessen mit irrelevanten Eigenschaften (sowie definierten Schließungen) verunreinigen kann, die nicht in der Klasse selbst deklariert wurden.

In der Klasse, in die Sie sich deserialisieren möchten, würde ich sicherstellen, dass alle Eigenschaften definiert werden, die Sie deserialisieren möchten (null, leeres Array usw.). Durch das Definieren Ihrer Eigenschaften mit Anfangswerten machen Sie deren Sichtbarkeit sichtbar, wenn Sie versuchen, Klassenmitglieder zu iterieren, denen Werte zugewiesen werden (siehe Deserialisierungsmethode 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) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

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

Im obigen Beispiel habe ich einfach eine Deserialisierungsmethode erstellt. In einem realen Beispiel hätte ich es in einer wiederverwendbaren Basisklasse oder Servicemethode zentralisiert.

Hier ist, wie man dies in etwas wie einem http verwenden kann ...

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

Wenn sich tslint/ide darüber beschwert, dass der Argumenttyp inkompatibel ist, können Sie das Argument mit spitzen Klammern <YourClassName> in denselben Typ umwandeln. Beispiel:

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

Wenn Sie Klassenmitglieder haben, die von einem bestimmten Typ sind (auch bekannt als Instanz einer anderen Klasse), können Sie sie mit getter/setter-Methoden in typisierte Instanzen gießen.

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

  // ctor & deserialize methods...

  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 deserialisiert sowohl die Aufgaben als auch die Liste der Aufgaben in ihre jeweiligen Klasseninstanzen.

11
Timothy Perez

Es muss noch nicht automatisch geprüft werden, ob das vom Server empfangene JSON-Objekt die erwarteten (Lesevorgänge entspricht) der Interface-Eigenschaften des TypeScript. Sie können jedoch Benutzerdefinierte Typenschutz verwenden.

In Anbetracht der folgenden Schnittstelle und eines dummen Json-Objekts (es könnte ein beliebiger Typ gewesen sein):

interface MyInterface {
    key: string;
 }

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

Drei Möglichkeiten:

A. Typ Assertion oder einfacher statischer Cast nach der Variablen

const myObject: MyInterface = json as MyInterface;

B. Einfacher statischer Guss vor der Variable und zwischen den Diamanten

const myObject: MyInterface = <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 spielen mit diesem Beispiel hier

Beachten Sie, dass hier die Schwierigkeit besteht, die Funktion isMyInterface zu schreiben. Ich hoffe, dass TS früher oder später einen Dekorator zu komplexe Typisierung exportieren zur Laufzeit hinzufügen wird und die Laufzeit die Struktur des Objekts bei Bedarf überprüfen lässt. Für den Moment könnten Sie entweder einen Json-Schema-Validator verwenden, dessen Zweck ungefähr gleich ist OR this Generator für Funktionsüberprüfung der Laufzeittypprüfung

6
Flavien Volken

Es ist zwar kein Casting per say; Ich habe festgestellt, dass https://github.com/JohnWhiteTB/TypedJSON eine nützliche Alternative ist. 

@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"
2
Neil

Ich habe diese Bibliothek hier verwendet: https://github.com/pleerock/class-transformer

<script lang="ts">
    import { plainToClass } from 'class-transformer';
</script>

Implementierung:

private async getClassTypeValue() {
  const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}

Manchmal müssen Sie die JSON-Werte für plainToClass analysieren, um zu verstehen, dass es sich um JSON-formatierte Daten handelt

1
Mac Chibueze

Eine alte Frage mit meist richtigen, aber nicht sehr effizienten Antworten. Was ich vorschlage:

Erstellen Sie eine Basisklasse, die init () - Methode und statische Cast-Methoden (für ein einzelnes Objekt und ein Array) enthält. Die statischen Methoden könnten überall sein; Die Version mit der Basisklasse und init () erlaubt danach einfache Erweiterungen.

export class ContentItem {
    // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
    static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
        // if we already have the correct class skip the cast
        if (doc instanceof proto) { return doc; }
        // create a new object (create), and copy over all properties (assign)
        const d: T = Object.create(proto.prototype);
        Object.assign(d, doc);
        // reason to extend the base class - we want to be able to call init() after cast
        d.init(); 
        return d;
    }
    // another method casts an array
    static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

Ähnliche Mechanismen (mit assign () ) wurden in @ Adam111p post erwähnt. Nur eine weitere (umfassendere) Möglichkeit, dies zu tun. @Timothy Perez ist kritisch gegenüber assign () , aber imho ist es hier durchaus angebracht.

Implementiere eine abgeleitete (echte) Klasse:

import { ContentItem } from './content-item';

export class SubjectArea extends ContentItem {
    id: number;
    title: string;
    areas: SubjectArea[]; // contains embedded objects
    depth: number;

    // method will be unavailable unless we use cast
    lead(): string {
        return '. '.repeat(this.depth);
    }

    // in case we have embedded objects, call cast on them here
    init() {
        if (this.areas) {
            this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
        }
    }
}

Jetzt können wir ein vom Dienst abgerufenes Objekt umsetzen:

const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);

Alle Hierarchien von SubjectArea Objekten haben die korrekte Klasse.

Ein Anwendungsfall/Beispiel; Erstellen Sie einen Angular-Service (wieder abstrakte Basisklasse):

export abstract class BaseService<T extends ContentItem> {
  BASE_URL = 'http://Host:port/';
  protected abstract http: Http;
  abstract path: string;
  abstract subClass: typeof ContentItem;

  cast(source: T): T {
    return ContentItem.castAs(source, this.subClass);
  }
  castAll(source: T[]): T[] {
    return ContentItem.castAllAs(source, this.subClass);
  }

  constructor() { }

  get(): Promise<T[]> {
    const value = this.http.get(`${this.BASE_URL}${this.path}`)
      .toPromise()
      .then(response => {
        const items: T[] = this.castAll(response.json());
        return items;
      });
    return value;
  }
}

Die Verwendung wird sehr einfach; Erstellen Sie einen Bereichsservice:

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

  constructor(protected http: Http) { super(); }
}

get () Die Methode des Service gibt ein Promise eines Arrays zurück, das bereits als SubjectArea objects (ganze Hierarchie) umgewandelt wurde.

Nun sagen wir, wir haben eine andere Klasse: 

export class OtherItem extends ContentItem {...}

Das Erstellen eines Dienstes, der Daten abruft und in die richtige Klasse konvertiert, ist so einfach wie folgt:

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

  constructor(protected http: Http) { super(); }
}

Ich habe dazu ein einfaches Tool erstellt https://beshanoe.github.io/json2ts/ Im Gegensatz zu json2ts.com funktioniert es direkt im Browser und sendet keine Daten an unbekannte Server. Es hat auch mehrere Einstellungen. Ich werde daran arbeiten, die Funktionalität zu verbessern

1
beshanoe

In den letzten TS kannst du folgendes machen:

const isMyInterface = (val: any): val is MyInterface => {
  if (!val) { return false; }
  if (!val.myProp) { return false; }
  return true;
};

Und als Benutzer so:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
0
Jaroslav

Ich hatte ein ähnliches Bedürfnis. Ich wollte etwas, das mir eine einfache Transformation von/zu JSON ermöglicht, die von einem REST API-Aufruf zu/von einer bestimmten Klassendefinition stammt. Die Lösungen, die ich gefunden habe, waren unzureichend oder dazu gedacht, den Code meiner Klassen neu zu schreiben und Anmerkungen oder ähnliche Elemente hinzuzufügen.

Ich wollte, dass GSON in Java verwendet wird, um Klassen für/von JSON-Objekten zu serialisieren/zu deserialisieren.

In Kombination mit dem späteren Bedarf, dass der Konverter auch in JS funktioniert, habe ich mein eigenes Paket geschrieben.

Es hat aber ein bisschen Overhead. Aber zu Beginn ist es sehr praktisch beim Hinzufügen und Bearbeiten.

Sie initialisieren das Modul mit:

  1. konvertierungsschema - ermöglicht die Zuordnung zwischen Feldern und legt fest, wie die Konvertierung durchgeführt wird
  2. Klassen-Kartenarray
  3. Konvertierungsfunktionskarte - für spezielle Konvertierungen.

Dann verwenden Sie in Ihrem Code das initialisierte Modul wie folgt:

const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');

const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');

oder zu JSON:

const jsonObject = this.converter.convertToJson(myClassInstance);

Verwenden Sie diesen Link zum Paket npm und eine ausführliche Erklärung zur Arbeit mit dem Modul: json-class-converter

Auch eingewickelt für
Angular Verwendung in: Angular-Json-Class-Converter

0
Doronm

Wir haben als .NET-Shop dieses Tool erstellt ( https://github.com/Centeva/TypeScripter ).

Es erstellt TypeScript-Klassen aus unseren DLLs. Wir müssen nur unsere Antwort json als Parameter in die Klasse leiten. Es funktioniert glatt für uns.

0
Devcon

Ich sehe keine Erwähnung von json-TypeScript-mapper: https://www.npmjs.com/package/json-TypeScript-mapper . Es sieht aus wie eine Kombination aus @ PhilipMiglincis Fund und @ Paks Antwort, soweit ich das beurteilen kann.

0
BBaysinger

Ich verwende Angular 6 auf dem Frontend und Spring Boot-Anwendung auf dem Backend, die Java Objekte zurückgibt. Alles, was ich tun muss, ist, eine ähnliche Klasse in der angular - Anwendung zu definieren, die übereinstimmende Eigenschaften hat, und dann kann ich das Objekt als angular - Klassenobjekt akzeptieren (comp als Firma im folgenden Beispiel).

Beziehen Sie sich beispielsweise auf den folgenden Front-End-Code. Lassen Sie mich in Kommentaren wissen, wenn etwas mehr Klarheit benötigt.

  createCompany() {
    let company = new Company();
    company.name = this.companyName;

    this.companyService.createCompany(company).subscribe(comp => {
       if (comp !== null) {
        this.employerAdminCompany = comp as Company;
      }
    });
  }

dabei ist company ein Entity-Objekt in der Spring-Boot-App und eine Klasse in Angular.

export class Company {
    public id: number;
    public name: string;
    public owner: User;
    public locations: Array<Location>;
}
0
Maneesh Parihar

Sie können eine interface Ihres Typs (SomeType) erstellen und das Objekt in diese umwandeln.

const typedObject: SomeType = <SomeType> responseObject;
0
Jayant Varshney