Objekt in TypeScript auf die Schnittstelle übertragen

99

Ich versuche, meinen Code vom Text einer Anfrage in Express (mithilfe der Body-Parser-Middleware) in eine Schnittstelle umzuwandeln, erzwingt jedoch keine Typensicherheit.

Dies ist meine Schnittstelle:

export interface IToDoDto {
  description: string;
  status: boolean;
};

Dies ist der Code, in dem ich versuche, die Besetzung zu machen:

@Post()
addToDo(@Response() res, @Request() req) {
  const toDo: IToDoDto = <IToDoDto> req.body; // <<< cast here
  this.toDoService.addToDo(toDo);
  return res.status(HttpStatus.CREATED).end();
}

Und schließlich die Servicemethode, die aufgerufen wird:

public addToDo(toDo: IToDoDto): void {
  toDo.id = this.idCounter;
  this.todos.push(toDo);
  this.idCounter++;
}

Ich kann alle Argumente übergeben, auch solche, die nicht annähernd mit der Schnittstellendefinition übereinstimmen , und dieser Code funktioniert einwandfrei. Ich würde erwarten, dass zur Laufzeit eine Ausnahme wie Java oder C # ausgelöst wird, wenn die Umwandlung vom Antworttext in die Schnittstelle nicht möglich ist.

Ich habe gelesen, dass in TypeScript kein Casting vorhanden ist, sondern nur Type Assertion. Daher wird dem Compiler nur mitgeteilt, dass ein Objekt vom Typ xist. Also ... irre ich mich? Was ist der richtige Weg, um die Typensicherheit durchzusetzen und zu gewährleisten?

Elias Garcia
quelle
1
Bitte definieren Sie "es funktioniert nicht". Sei genau. Gibt es einen Fehler? Welcher? Zur Kompilierungszeit? Zur Laufzeit? Was geschieht?
JB Nizet
1
Zur Laufzeit wird der Code normal ausgeführt, unabhängig davon, welches Objekt ich übergebe.
Elias Garcia
Es ist nicht klar, was Sie fragen
Nitzan Tomer
Meine Frage ist, wie man das eingehende Objekt in ein typisiertes Objekt umwandelt. Wenn die Besetzung nicht möglich ist, werfen Sie zur Laufzeit eine Ausnahme wie Java, C # ...
Elias Garcia
Beantwortet das deine Frage? TypeScript- oder JavaScript-Casting
Michael Freidgeim

Antworten:

141

Es gibt kein Casting in Javascript, daher können Sie nicht werfen, wenn "Casting fehlschlägt".
Typescript unterstützt das Casting, dies gilt jedoch nur für die Kompilierungszeit. Sie können dies folgendermaßen tun:

const toDo = <IToDoDto> req.body;
// or
const toDo = req.body as IToDoDto;

Sie können zur Laufzeit überprüfen, ob der Wert gültig ist, und wenn nicht, einen Fehler auslösen, dh:

function isToDoDto(obj: any): obj is IToDoDto {
    return typeof obj.description === "string" && typeof obj.status === "boolean";
}

@Post()
addToDo(@Response() res, @Request() req) {
    if (!isToDoDto(req.body)) {
        throw new Error("invalid request");
    }

    const toDo = req.body as IToDoDto;
    this.toDoService.addToDo(toDo);
    return res.status(HttpStatus.CREATED).end();
}

Bearbeiten

Wie @huyz hervorhob, ist die Typzusicherung nicht erforderlich, da isToDoDtoes sich um einen Typschutz handelt. Dies sollte also ausreichen:

if (!isToDoDto(req.body)) {
    throw new Error("invalid request");
}

this.toDoService.addToDo(req.body);
Nitzan Tomer
quelle
Ich glaube nicht, dass Sie die Besetzung brauchen, const toDo = req.body as IToDoDto;da der TS-Compiler weiß, dass es IToDoDtozu diesem Zeitpunkt ein ist
huyz
9
Verwenden Sie für alle, die generell nach Typzusicherung suchen, nicht <>. das ist veraltet. Verwenden Sieas
Abhishek Deb
" Es gibt kein Casting in Javascript, also können Sie nicht werfen, wenn" Casting fehlschlägt ". " Ich denke, genauer gesagt, Schnittstellen in TypeScript sind nicht umsetzbar. Tatsächlich sind sie 100% syntatischer Zucker . Sie erleichtern die konzeptionelle Pflege von Strukturen , haben jedoch keine tatsächlichen Auswirkungen auf den transpilierten Code - was imo wahnsinnig verwirrend / anti-muster ist, wie die Frage von OP zeigt. Es gibt keinen Grund, warum Dinge, die nicht mit Schnittstellen übereinstimmen, transpiliertes JavaScript nicht einwerfen können. Es ist eine bewusste (und schlechte, imo) Wahl von TypeScript.
Ruffin
@ ruffin-Interfaces sind kein syntaktischer Zucker, aber sie haben sich bewusst dafür entschieden, ihn nur zur Laufzeit beizubehalten. Ich denke, es ist eine gute Wahl, so dass es zur Laufzeit keine Leistungseinbußen gibt.
Nitzan Tomer
Tomayto Tomahto ? Die Typensicherheit von Schnittstellen in TypeScript erstreckt sich nicht auf Ihren transpilierten Code, und selbst vor der Laufzeit ist die Typensicherheit stark eingeschränkt - wie wir in der OP-Ausgabe sehen, in der es überhaupt keine Typensicherheit gibt . TS könnte sagen: "Hey, warte, das anyist noch nicht garantiert IToDoDto!", Aber TS hat sich dagegen entschieden. Wenn der Compiler nur fängt einige Typ Konflikte, und keiner in der transpiled Code (und Sie haben Recht, ich sollte mehr klar @ gewesen bin , dass im Original), Schnittstellen leider sind, imo, [meistens?] Zucker.
Ruffin
7

Hier ist eine andere Möglichkeit, eine Typumwandlung auch zwischen inkompatiblen Typen und Schnittstellen zu erzwingen, bei denen sich der TS-Compiler normalerweise beschwert:

export function forceCast<T>(input: any): T {

  // ... do runtime checks here

  // @ts-ignore <-- forces TS compiler to compile this as-is
  return input;
}

Dann können Sie es verwenden, um gegossene Objekte auf einen bestimmten Typ zu zwingen:

import { forceCast } from './forceCast';

const randomObject: any = {};
const typedObject = forceCast<IToDoDto>(randomObject);

Beachten Sie, dass ich den Teil, den Sie zur Laufzeitprüfung durchführen sollen, vor dem Casting weggelassen habe, um die Komplexität zu verringern. In meinem Projekt kompiliere ich alle meine .d.tsSchnittstellendateien in JSON-Schemas und überprüfe sie ajvzur Laufzeit.

Sepehr
quelle
2

Wenn es jemandem hilft, hatte ich ein Problem, bei dem ich ein Objekt als einen anderen Typ mit einer ähnlichen Schnittstelle behandeln wollte. Ich habe Folgendes versucht:

Fusseln nicht bestanden

const x = new Obj(a as b);

Der Linter beschwerte sich, dass aEigenschaften fehlten, die auf existierten b. Mit anderen Worten, ahatte einige Eigenschaften und Methoden b, aber nicht alle. Um dies zu umgehen, folgte ich dem Vorschlag von VS Code:

Flusen und Testen bestanden

const x = new Obj(a as unknown as b);

Beachten Sie, dass Sie einen Laufzeitfehler feststellen sollten , wenn Ihr Code versucht, eine der Eigenschaften aufzurufen, die für einen Typ vorhanden sind, der für einen Typ bnicht implementiert ist a.

Jason
quelle
1
Ich bin froh, dass ich diese Antwort gefunden habe, aber beachten Sie, dass beim Senden von 'x' über das Netzwerk oder an eine andere App möglicherweise persönliche Daten verloren gehen (wenn 'a' beispielsweise ein Benutzer ist), da 'x' immer noch vorhanden ist hat alle Eigenschaften von 'a', sie sind nur für Typoskript nicht verfügbar.
Zoltán Matók
@ ZoltánMatók guter Punkt. In Bezug auf das Senden des serialisierten Objekts über das Netzwerk gibt es auch ein Argument für Getter und Setter im Java-Stil über JavaScript getund setMethoden.
Jason