JSON zu TypeScript
Erzeugt TypeScript-Interfaces aus JSON: Array-Elemente zusammengeführt, optionale Schlüssel, Unions gemischter Typen, null getrennt — alles im Browser.
export interface Root {
id: number
name: string
active: boolean
address: Address
roles: string[]
posts: Post[]
tags: unknown[]
}
export interface Address {
city: string
zip: null
}
export interface Post {
id: number
title: string
views: number | string
pinned?: boolean
}
Interfaces: 3
Typen aus den Daten abgeleitet, nicht geraten
Ein JSON-Dokument trägt nicht seine eigenen Typen — es trägt Werte, und die Typen müssen daraus zurückgelesen werden. Das ist für ein einzelnes Objekt leicht und für eine Sammlung überraschend heikel: Die Form, die Sie wollen, ist nicht die Form irgendeines einzelnen Datensatzes, sondern die Form, die jeder Datensatz erfüllen muss. Dieses Werkzeug leitet TypeScript-Interfaces aus einer JSON-Probe ab, indem es alles betrachtet, was die Probe enthält, sodass die erzeugten Typen die Gesamtheit Ihrer Daten beschreiben und nicht die erste Zeile, die zufällig obenauf lag.
Die Umwandlung geht nur in eine Richtung. Typen zurück in JSON zu verwandeln hieße, Werte zu erfinden, und hier ist das Ziel das Gegenteil: Das eingefügte Dokument ist die Quelle der Wahrheit, und jede Deklaration wird daraus abgeleitet. Fügen Sie eine API-Antwort, eine Konfigurationsdatei oder eine Log-Zeile ein und lesen Sie das Interface ab, das Sie sonst von Hand geschrieben hätten.
Wie Arrays zusammengeführt werden
Die interessanten Entscheidungen fallen alle bei den Arrays. Ein naiver Konverter betrachtet das erste Element und hört auf, wodurch jedes Feld, das er zufällig sieht, erforderlich wirkt und jedes Feld, das er nicht sieht, fehlt. Dieses Werkzeug führt stattdessen alle Elemente zu einem einzigen Typ zusammen, und daraus ergeben sich drei Dinge:
- Ein Schlüssel, der in manchen Elementen vorhanden, in anderen aber abwesend ist, wird optional, geschrieben mit einem Fragezeichen. Wenn die Hälfte Ihrer Datensätze ein "middleName" hat und die Hälfte nicht, ist das Feld "middleName?", genau das, was ein Konsument behandeln muss.
- Ein Schlüssel, dessen Wert sich in der Art zwischen Elementen unterscheidet, wird zu einer Union. Ein Feld, das in einem Datensatz eine Zahl und in einem anderen ein String ist, wird als "number | string" typisiert — nicht der Ordnung halber, sondern weil es das ist, was die Daten tatsächlich enthalten und was Ihr Code akzeptieren muss.
- Ein verschachteltes Objekt innerhalb der Elemente wird auf dieselbe Weise zusammengeführt, rekursiv, und in ein eigenes Interface ausgelagert. Zehn Array-Elemente, die je ein "address" tragen, ergeben ein einziges Address-Interface, das alle zehn beschreibt.
Ist das Dokument selbst ein Array auf oberster Ebene, wird die Wurzel zu einem Alias auf ein Element-Interface — etwa "type Root = RootItem[]" — mit dem zusammengeführten Elementtyp darunter.
Null, optional und warum sie verschieden sind
Es ist verlockend, ein null wie einen fehlenden Schlüssel zu behandeln, und falsch. In TypeScript bedeutet "name?: string", dass die Eigenschaft fehlen darf; "name: string | null" bedeutet, dass sie immer da ist, aber null enthalten darf. Das sind verschiedene Verträge, und ein Konsument prüft sie verschieden — "in" gegenüber einem Wertvergleich. Dieses Werkzeug hält sie auseinander: Ein explizites null in den Daten wird zu einem Union-Mitglied "| null", zuletzt angeordnet, damit "string | null" sich so liest, wie Sie es erwarten, während ein Schlüssel, der schlicht in manchen Datensätzen fehlt, optional wird. Ein Feld, das beides ist — null in einem Datensatz, fehlend in einem anderen — kommt als beides heraus, "field?: T | null", weil beide Tatsachen für Ihre Daten gelten.
Verschachtelte Objekte werden benannte Interfaces
Statt eine verschachtelte Form in ihren Elternteil einzubetten, wird jedes Objekt in ein eigenes Interface ausgelagert, dessen Name aus dem Schlüssel abgeleitet wird, unter dem es steht. Ein "user"-Objekt wird zu einem User-Interface; ein "address" darin wird zu einem Address-Interface, auf das User verweist. Tief eingebettete Typen sind schwer zu lesen und unmöglich wiederzuverwenden, und benannte Interfaces sind das, was Sie selbst geschrieben hätten. Array-Elemente werden in den Singular gesetzt, wo möglich — "users" ergibt ein User, "categories" ein Category — und ein Schlüssel, der sich nicht pluralisieren lässt, erhält das Suffix Item, damit das Element einen eigenen Namen hat.
Würden zwei verschiedene Objekte denselben Namen erhalten — zwei unzusammenhängende "data" etwa — bekommt das zweite ein Suffix, statt zusammengeführt zu werden, sodass verschiedene Formen verschieden bleiben. Das Wurzelobjekt wird zuerst ausgegeben und Sie können es umbenennen; die Wahl zwischen "interface"- und "type"-Ausgabe ist ein Schalter, da einige Codebasen durchgehend Typ-Aliase bevorzugen.
Der Rückfall auf unknown
Manche Werte tragen überhaupt keine Typinformation. Ein leeres Array könnte alles enthalten; ein leeres Objekt hat keine Schlüssel zu beschreiben. Statt zu "any" zu greifen — das die Typprüfung für alles Folgende abschaltet — fällt das Werkzeug auf "unknown" zurück: Ein leeres Array wird zu "unknown[]", ein leeres Objekt zu "Record<string, unknown>". Der Unterschied zählt. "any" lässt Fehler stillschweigend durch; "unknown" zwingt den Konsumenten, den Wert vor der Verwendung einzugrenzen, sodass der abgeleitete Typ ehrlich bleibt darüber, was die Probe gesagt hat und was nicht.
Das sind die Typen, die Sie von Hand verschärfen würden, sobald Sie wissen, was die leere Sammlung enthalten soll — aber bis die Daten es sagen, ist "unknown" die ehrliche Antwort, und es ist die, die den Rest Ihrer Typen sicher hält.
Worauf es läuft, und wo
Alles geschieht in Ihrem Browser. Das JSON wird geparst und die Typen werden auf Ihrem eigenen Gerät abgeleitet; nichts, was Sie einfügen, wird hochgeladen, gespeichert oder protokolliert. Das macht das Werkzeug sicher für eine echte API-Antwort oder eine Konfigurationsdatei mit Geheimnissen darin — die Probe verlässt nie die Seite. Die Ausgabe ist gewöhnliches TypeScript, das Sie direkt in eine "d.ts"-Datei oder ein Modul einfügen, die wenigen "unknown"-Felder anpassen, die die Daten nicht beschreiben konnten, und verwenden können.
Häufig gestellte Fragen
- Wird mein JSON an einen Server gesendet?
- Nein. Das Dokument wird geparst und die Typen werden vollständig in Ihrem Browser abgeleitet, und nichts, was Sie einfügen, wird hochgeladen oder protokolliert. Es ist sicher für eine echte API-Antwort oder eine Konfigurationsdatei.
- Warum ist ein Feld optional, obwohl es in meiner Probe vorhanden ist?
- Weil es in mindestens einem Element eines Arrays fehlt, das das Werkzeug zusammengeführt hat. Die Inferenz liest jedes Element, nicht nur das erste, sodass ein Schlüssel, den manche Datensätze weglassen, optional wird — das ist der Typ, den Ihre Daten tatsächlich zulassen, auch wenn der Datensatz, den Sie angesehen haben, ihn zufällig enthielt.
- Warum wurde ein Feld zu einer Union wie string | number?
- Weil der Wert in verschiedenen Array-Elementen verschiedene Arten hatte — ein String in einem Datensatz und eine Zahl in einem anderen. Der zusammengeführte Typ muss beide akzeptieren, also wird er als Union geschrieben. Wenn Sie das überrascht, bedeutet es meist, dass die Daten weniger einheitlich sind als erwartet, was zu wissen sich lohnt.
- Warum verwendet das Werkzeug unknown statt any?
- Für Werte, die es nicht beschreiben kann — ein leeres Array, ein leeres Objekt — hält "unknown" das Ergebnis typsicher und zwingt einen Konsumenten, den Wert vor der Verwendung einzugrenzen, während "any" die Typprüfung abschalten würde. Sie können diese Felder von Hand verschärfen, sobald Sie wissen, was die leere Sammlung enthält.
- Was ist der Unterschied zwischen der interface- und der type-Ausgabe?
- Keiner in den Typen, die sie beschreiben — beide erzeugen dieselben Formen. "interface" ist das übliche Idiom für Objekttypen und lässt sich erweitern und zusammenführen; "type"-Aliase sind das, was einige Codebasen durchgehend bevorzugen. Der Schalter ist da, damit die Ausgabe zum Stil Ihres Projekts passt.
- Kann es TypeScript zurück in JSON verwandeln?
- Nein, und bewusst. Die Umwandlung ist einseitig: JSON hinein, Typen hinaus. Die andere Richtung hieße, Werte zu erfinden, die nie in Ihren Daten waren, und der ganze Sinn ist, dass jede Deklaration aus dem abgeleitet wird, was Sie tatsächlich eingefügt haben.
- Wie werden verschachtelte Objekte benannt?
- Nach dem Schlüssel, unter dem sie stehen: Ein "user"-Objekt wird zu User, ein "address" darin zu Address. Array-Elemente werden in den Singular gesetzt, wo möglich — "categories" ergibt Category — und ein Schlüssel, der sich nicht pluralisieren lässt, erhält das Suffix Item. Zwei verschiedene Formen, die bei einem Namen kollidieren würden, erhalten ein Suffix, statt zusammengeführt zu werden, sodass sie verschieden bleiben.