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!

6voto

Rodney P. Barbati Punkte 1551

Persönlich finde ich es entsetzlich, dass TypeScript es nicht erlaubt, eine Endpunktdefinition anzugeben, welche Art von Objekt empfangen wird. Da dies tatsächlich der Fall zu sein scheint, würde ich das tun, was ich mit anderen Sprachen gemacht habe, nämlich das JSON-Objekt von der Klassendefinition trennen und die Klassendefinition das JSON-Objekt als einziges Datenelement verwenden lassen.

Ich verabscheue Boilerplate-Code, daher geht es für mich in der Regel darum, das gewünschte Ergebnis mit möglichst wenig Code zu erreichen, während ich den Typ beibehalte.

Betrachten Sie die folgenden JSON-Objektstrukturdefinitionen - dies wären die Strukturdefinitionen, die Sie an einem Endpunkt erhalten würden, es handelt sich nur um Strukturdefinitionen, keine Methoden.

interface IAdresse {
    strasse: string;
    stadt: string;
    bundesland: string;
    plz: string;
}

interface IPerson {
    name: string;
    adresse: IAdresse;
}

Wenn wir das obige in objektorientierten Begriffen betrachten, sind die obigen Schnittstellen keine Klassen, weil sie nur eine Datenstruktur definieren. Eine Klasse in OO-Begriffen definiert Daten und den Code, der darauf ausgeführt wird.

Also definieren wir jetzt eine Klasse, die Daten und den Code definiert, der darauf ausgeführt wird...

class Person {
    person: IPerson;

    constructor(person: IPerson) {
        this.person = person;
    }

    // Zugriffsmethoden
    getName(): string {
        return person.name;
    }

    getAdresse(): IAdresse {
        return person.adresse;
    }

    // Sie könnten einen generischen Getter für jeden Wert in der Person schreiben, 
    // egal wie tief er liegt, indem Sie eine variable Anzahl von Zeichenfolgenparametern akzeptieren

    // Methoden
    distanceFrom(adresse: IAdresse): float {
        // Berechnen Sie die Entfernung von der übergebenen Adresse zu der Adresse dieser Person
        return 0.0;
    }
}

Und jetzt können wir einfach ein beliebiges Objekt übergeben, das der IPerson-Struktur entspricht, und weitermachen...

   Person person = new Person({
            name: "Name der Person",
            adresse: {
                strasse: "Eine Straßenadresse",
                stadt: "eine Stadt",
                bundesland: "ein Bundesland",
                plz: "Eine Postleitzahl"
            }
        });

Auf die gleiche Weise können wir jetzt das Objekt verarbeiten, das an Ihrem Endpunkt empfangen wurde, etwa wie folgt...

Person person = new Person(req.body);    // Wie bei einem über einen POST-Aufruf empfangenen Objekt

person.distanceFrom({ strasse: "Eine Straßenadresse", usw. });

Dies ist wesentlich effizienter, verwendet die Hälfte des Arbeitsspeichers für die Datenkopie und reduziert erheblich den Boilerplate-Code, den Sie für jeden Entitätstyp schreiben müssen. Es verlässt sich einfach auf die Typsicherheit, die TypeScript bietet.

5voto

s_bighead Punkte 829

Wenn Sie Ihr JSON-Objekt in eine Typskriptklasse umwandeln müssen und ihre Instanzmethoden im resultierenden Objekt verfügbar haben möchten, müssen Sie Object.setPrototypeOf verwenden, wie ich es im folgenden Code-Snippet getan habe:

Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)

5voto

Sam Punkte 1428

Verwenden Sie eine Klasse, die von einem Interface erweitert wird.

Dann:

    Object.assign(
        new ToWhat(),
        what
    )

Und am besten:

    Object.assign(
        new ToWhat(),
        what
    )

ToWhat wird ein Controller von DataInterface

4voto

Eine alte Frage mit meist korrekten, aber nicht sehr effizienten Antworten. Dies ist mein Vorschlag:

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

export class ContentItem {
    // Parameter: doc - plain JS-Objekt, proto - Klasse, zu der wir casten wollen (Unterklasse von ContentItem)
    static castAs(doc: T, proto: typeof ContentItem): T {
        // Wenn wir bereits die richtige Klasse haben, überspringen wir den Cast
        if (doc instanceof proto) { return doc; }
        // Ein neues Objekt erstellen (create) und alle Eigenschaften kopieren (assign)
        const d: T = Object.create(proto.prototype);
        Object.assign(d, doc);
        // Grund für die Erweiterung der Basisklasse - wir möchten init() nach dem Cast aufrufen können
        d.init(); 
        return d;
    }
    // Eine andere Methode castet ein Array
    static castAllAs(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

Ähnliche Mechanismen (mit assign()) wurden im Beitrag von @Adam111p erwähnt. Nur ein weiterer (vollständigerer) Weg, es zu tun. @Timothy Perez kritisiert assign(), aber meiner Meinung nach ist es hier vollkommen angemessen.

Implementieren Sie eine abgeleitete (die echte) Klasse:

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

export class SubjectArea extends ContentItem {
    id: number;
    title: string;
    areas: SubjectArea[]; // enthält eingebettete Objekte
    depth: number;

    // Die Methode wird nicht verfügbar sein, es sei denn, wir verwenden cast
    lead(): string {
        return '. '.repeat(this.depth);
    }

    // Falls wir eingebettete Objekte haben, rufen wir hier cast auf
    init() {
        if (this.areas) {
            this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
        }
    }
}

Jetzt können wir ein Objekt, das vom Service abgerufen wurde, casten:

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

Die gesamte Hierarchie von SubjectArea-Objekten wird die richtige Klasse haben.

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

export abstract class BaseService {
  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 {
    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 Area-Service:

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

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

Die get()-Methode des Dienstes gibt ein Promise eines Arrays zurück, das bereits als SubjectArea-Objekte (ganze Hierarchie) gecastet ist.

Nehmen wir jetzt an, wir haben eine andere Klasse:

export class OtherItem extends ContentItem {...}

Die Erstellung eines Dienstes, der Daten abruft und in die richtige Klasse castet, ist so einfach wie:

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

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

4voto

Cassio Seffrin Punkte 4409

Es gibt mehrere Möglichkeiten, es zu tun, lassen Sie uns einige Optionen untersuchen:

class Person {
   id: number | undefined;
   firstName: string | undefined;
   //? mark for note not required attribute.
   lastName?: string;
}

// Option 1: Füllen Sie ein beliebiges Attribut aus, und es wird akzeptiert.
const person1= { firstName: 'Cassio' } as Person ;
console.log(person1);

// Option 2. Alle Attribute müssen Daten zuweisen.
const  person2: Person = { id: 1, firstName: 'Cassio', lastName:'Seffrin' };  
console.log(person2);

//  Option 3. Verwenden Sie ein teilweises Interface, wenn nicht alle Attribute erforderlich sind.
const  person3: Partial = { firstName: 'Cassio' };  
console.log(person3);

//  Option 4. Da lastName optional ist, funktioniert es
const  person4: Person = { id:2, firstName: 'Cassio'  };
console.log(person4);

//  Option 5. Füllen Sie ein beliebiges Attribut aus, und es wird akzeptiert.
const person5 =  {firstName: 'Cassio'}; 
console.log(person5 );

Ergebnis:

[LOG]: {
  "firstName": "Cassio"
} 
[LOG]: {
  "id": 1,
  "firstName": "Cassio",
  "lastName": "Seffrin"
} 
[LOG]: {
  "firstName": "Cassio"
} 
[LOG]: {
  "id": 2,
  "firstName": "Cassio"
} 
[LOG]: {
  "firstName": "Cassio"
} 

Es funktioniert auch, wenn Sie ein Interface anstelle einer Typescript-Klasse haben.

interface PersonInterface {
   id: number;
   firstName: string;
   lastName?: string;
}

Führen Sie diesen Code aus

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