JSON.parse gibt any zurück, also akzeptiert TypeScript jeden Typ, dem du das Ergebnis zuweist. Das macht das Typisieren von geparstem JSON zu einer Zeile, bedeutet aber auch, dass der Typ ein Versprechen von dir ist und nichts, was der Compiler prüft:
Beim zweiten Objekt ist age der String "41". TypeScript nennt es trotzdem number, weil any allem zugewiesen werden kann, und das Programm gibt 411 aus. Für JSON, das dein eigener Code gerade erst geschrieben hat, ist die Annotation in Ordnung. Daten aus einem Request, einer Datei oder dem Local Storage validierst du.
In unknown parsen
Typisierst du das Ergebnis als unknown, besteht der Compiler auf einer Prüfung, bevor irgendeine Eigenschaft verwendet wird:
Der Fehler lautet index.ts(4,13): error TS18046: 'data' is of type 'unknown'. Jede Prüfung, die du dann schreibst, engt data ein Stück weiter ein.
Mit einem Type Guard validieren
Ein Type Guard ist eine Funktion, die value is User zurückgibt. Liefert sie true, behandelt TypeScript den Wert von da an als User, und die Prüfungen darin sind echte Prüfungen zur Laufzeit:
JSON.parse selbst wirft bei fehlerhaftem Text einen SyntaxError, echter Code packt es also zusätzlich in try/catch. Bei großen oder verschachtelten Daten werden handgeschriebene Guards lang; Schema-Bibliotheken wie Zod oder Valibot lassen dich die Form einmal deklarieren und daraus sowohl den Validator als auch den TypeScript-Typ ableiten.
JSON zu TypeScript-Interface
Ein JSON-Beispiel in Typen umzuwandeln ist Handarbeit nach Schema. Gegeben diese Antwort:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
Ordne jedem Wert seinen Typ zu, gib verschachtelten Objekten ein eigenes Interface und markiere, was variieren kann:
Ein einzelnes Beispiel verrät nicht, welche Felder optional oder nullable sind. Sieh dir mehrere Antworten oder die Dokumentation der API an, bevor du dich auf ? und | null festlegst.
Datumswerte und der Reviver
JSON hat keinen Datumstyp, also kommen Datumswerte als Strings an. Das zweite Argument von JSON.parse, der Reviver, wird für jeden Schlüssel aufgerufen und kann sie wiederherstellen:
Der Parameter value des Revivers ist any, ebenso das Ergebnis, der Typ Order wird also weiterhin vertraut statt geprüft. JSON.stringify(order, null, 2) rückt die Ausgabe um zwei Leerzeichen ein und macht aus dem Date wieder seinen ISO-String.
JSON.stringify und was dabei verloren geht
JSON.stringify ist so typisiert, dass es string zurückgibt. Die Werte, die es umwandelt, kommen nicht immer gleich zurück, und der Typ warnt dich nicht:
| Wert | Nach JSON.stringify |
|---|---|
Date | ISO-String (über seine Methode toJSON) |
Map, Set | {} (vorher mit [...set] oder Object.fromEntries(map) umwandeln) |
undefined, Funktionen, Symbole in einem Objekt | der Schlüssel fällt weg |
undefined, Funktionen, Symbole in einem Array | null |
undefined, eine Funktion oder ein Symbol allein | undefined, kein String |
NaN, Infinity | null |
bigint | wirft einen TypeError |
Die Regeln zur Laufzeit sind dieselben wie in reinem JavaScript, beschrieben unter JSON in JavaScript.
Ein Typ für jeden JSON-Wert
Wenn Code beliebiges JSON verarbeitet, beschreibt ein rekursiver Typ genau, was JSON enthalten kann, und lehnt Werte ab, die es nicht kann:
Ohne den Kommentar ist die letzte Zeile ein Compilerfehler, weil ein Date-Objekt kein JsonValue ist.
.json-Dateien importieren
Eine .json-Datei lässt sich wie ein Modul importieren, und TypeScript leitet ihren Typ aus dem Inhalt ab:
{ "name": "app", "port": 8080, "tags": ["a"] }
// CommonJS output, or a bundler
import config from "./config.json";
const port: number = config.port; // typed from the file: number
// An ES module under module: nodenext
import settings from "./config.json" with { type: "json" };
In TypeScript 7 funktioniert das ohne zusätzliche Einstellungen, wenn module auf nodenext, node20, commonjs, esnext oder preserve steht. Unter node16 und node18 scheitert es mit dem Fehler TS2732, Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension., bis du "resolveJsonModule": true ergänzt; auf false gesetzt schaltet die Option JSON-Imports überall ab. In einem ES-Modul unter nodenext oder node20 braucht der Import das Attribut with { type: "json" } (ohne gibt es den Fehler TS1543), und nur der Default-Import ist erlaubt (Fehler TS1544 bei import { port }). tsc kopiert die importierte .json-Datei nach outDir, neben das kompilierte JavaScript.
Häufig gestellte Fragen
Welchen Typ gibt JSON.parse in TypeScript zurück?
any. Der Compiler kann nicht wissen, was ein String enthält, also kompiliert const user: User = JSON.parse(text) unabhängig vom Inhalt des Texts. Weise das Ergebnis unknown zu und validiere es, wenn die Daten von außerhalb deines Programms kommen.
Wie wandle ich JSON in ein TypeScript-Interface um?
Nimm ein repräsentatives Beispiel und schreibe pro Schlüssel eine Eigenschaft: Strings, Zahlen und Booleans werden zu string, number und boolean, ein verschachteltes Objekt bekommt ein eigenes Interface, ein Array von Objekten wird zu Item[], und Schlüssel, die manchmal fehlen, bekommen ein ?. Codegeneratoren wie quicktype automatisieren das, aber prüfe ihre Vermutungen an mehr als einem Beispiel.
Wie importiere ich eine JSON-Datei in TypeScript?
import config from "./config.json"; funktioniert in TypeScript 7, wenn module auf nodenext, node20, commonjs, esnext oder preserve steht, und das Ergebnis wird aus dem Inhalt der Datei typisiert. Mit node16 oder node18 setzt du zusätzlich "resolveJsonModule": true. In einem ES-Modul unter nodenext oder node20 ergänzt du das Attribut, das Node verlangt: import config from "./config.json" with { type: "json" };.
Gibt JSON.stringify immer einen String zurück?
Laut Typ string, aber JSON.stringify(undefined) und JSON.stringify(() => 1) geben zur Laufzeit undefined zurück. Auch Werte innerhalb von Objekten werden umgewandelt: Ein Date wird zu einem ISO-String, und Map und Set werden zu {}.