JSON zu Zod
Erzeugt Zod-Schemas aus JSON, jedes mit seinem z.infer-Typ: Array-Elemente zusammengeführt, optionale und nullable Schlüssel markiert, nichts geraten.
import * as z from "zod"
export const Customer = z.object({
name: z.string(),
email: z.string(),
phone: z.null(),
})
export type Customer = z.infer<typeof Customer>
export const LineItem = z.object({
sku: z.string(),
quantity: z.number(),
price: z.number(),
note: z.string().nullable(),
backordered: z.boolean().optional(),
})
export type LineItem = z.infer<typeof LineItem>
export const Root = z.object({
id: z.string(),
customer: Customer,
lineItems: z.array(LineItem),
tags: z.array(z.unknown()),
})
export type Root = z.infer<typeof Root>
Zod-4-Syntax, die auch unter Zod 3 gültig ist. z.object verwirft jeden Schlüssel, den es nicht aufführt: Ein Feld, das Ihrem Beispiel fehlte, verschwindet stillschweigend aus den geparsten Daten.
An jeder unten aufgeführten Position enthielt Ihr Beispiel nur null, also akzeptiert das Schema dort nichts anderes.
Positionen: 1
Customer.phone
An jeder unten aufgeführten Position war jede Zahl ganzzahlig. z.number() akzeptiert auch 7.5, und .int() würde ganze Zahlen verlangen: Fügen Sie es nur dort selbst hinzu, wo Sie wissen, dass ein Wert ganzzahlig sein muss.
Positionen: 1
LineItem.quantity
An jeder unten aufgeführten Position ließ sich nichts ableiten, also prüft das Schema dort nicht, was vorliegt: z.unknown() akzeptiert jeden Wert und z.record(z.string(), z.unknown()) jedes Objekt.
Positionen: 1
Root.tags[]
Eine Prüfung, die jedes Mal läuft, wenn die Daten eintreffen
Ein TypeScript-Typ wird geprüft, wenn Ihr Code kompiliert wird, und ist nicht mehr vorhanden, wenn der Code läuft. Ein Zod-Schema ist der Teil, der bleibt: Es läuft in Ihrem Programm und prüft jede Nutzlast beim Eintreffen — eine API-Antwort, den Rumpf eines Webhooks, eine Nachricht aus einer Warteschlange —, bevor sich Ihr Code auf sie verlässt. Fügen Sie ein Beispiel dieses JSON ein, und diese Seite schreibt die Schemas für Sie, fertig zum Einfügen in ein Modul: Jedes Objekt, das Schlüssel hat, wird zu einem benannten "z.object", die Elemente eines Arrays zu einem zusammengeführt, und auf jedes Schema folgt der TypeScript-Typ, den es erzeugt.
Es ist die Antwort, die die Seite JSON zu TypeScript für dasselbe JSON gibt, geschrieben als Validator statt als Typen. Beide Seiten lesen dieselbe Inferenz, sodass einmal entschieden und nur zweimal ausgeschrieben wird, welche Schlüssel optional sind, welche Werte zu einer Union werden, wo null von einem fehlenden Schlüssel getrennt gehalten wird und wie jedes verschachtelte Objekt heißt. Diese Regeln des Zusammenführens sind das Thema des Leitfadens auf der Seite JSON zu TypeScript und werden hier nicht noch einmal dargelegt. Dieser Leitfaden handelt davon, was das Schema mit echten Daten tut, sobald es läuft, und was es Ihnen zur Entscheidung überlässt.
Was durchkommt und was abgelehnt wird
Ein Schema von dieser Seite akzeptiert das Beispiel, aus dem es geschrieben wurde, unter Zod 3 wie unter Zod 4, außer in dem einen Fall, der einen eigenen Hinweis hat: bei einer unendlichen Zahl unter Zod 4. Über dieses Beispiel hinaus akzeptiert es alles, was innerhalb dessen bleibt, was das Schema sagt, und für die Daten, die Sie tatsächlich erhalten werden, sieht das so aus:
- Vor Erreichen der Tiefengrenze ist ein Schlüssel, der ohne ".optional()" geschrieben ist, erforderlich. Eine Nutzlast, die ihn weglässt, wird abgelehnt, und ebenso eine, die dort eine Art von Wert einsetzt, die das Schema nicht nennt — eine Zeichenkette, wo es "z.number()" sagt, ein Objekt, wo es "z.string()" sagt.
- Ein Schlüssel, der mit ".optional()" geschrieben ist, darf fehlen, und einer, der mit ".nullable()" geschrieben ist, darf null enthalten. Keiner von beiden lässt eine andere Art von Wert durch, also muss ein optionaler Schlüssel, der vorhanden ist, trotzdem enthalten, was das Schema nennt.
- Ein "z.union" nimmt jedes seiner Mitglieder und sonst nichts. Ein "z.array" nimmt beliebig viele Elemente, auch gar keines, solange jedes zu dem Schema passt, das für seine Elemente geschrieben ist.
- Wo das Beispiel nichts gezeigt hat — die Elemente eines leeren Arrays, ein Objekt ohne Schlüssel, ein Wert, der jenseits der Tiefengrenze verschachtelt ist —, prüft das Schema nicht, was dort vorliegt: Es nimmt jeden Wert oder, wo das Objekt des Beispiels keine Schlüssel hatte, jedes Objekt, und ein Hinweis sagt, wo.
Ein Schlüssel, den das Schema nicht aufführt, wird durchgelassen und dann aus dem Ergebnis verworfen; das ist das Verwerfen, und es hat einen eigenen Abschnitt. Ein Schlüssel steht außerhalb von alldem. Ein Schlüssel namens "__proto__" wird als berechneter Schlüssel in eckigen Klammern geschrieben, weil er, schlicht hingeschrieben, den Prototyp des Objekts setzen würde, in dem er steht, statt einen Schlüssel zu benennen — und doch gibt keine der beiden Zod-Versionen diesen Schlüssel in dem zurück, was das Parsen liefert, und Zod 4 prüft seinen Wert überhaupt nicht.
Warum sich das Schema nie selbst verschärft
Jedes Beispiel hat Gemeinsamkeiten, die es nicht beweisen kann: jede ID eine ganze Zahl, jede E-Mail wie eine E-Mail geformt, eine Rolle, die immer nur admin lautete. Das ist eine Regelmäßigkeit, und eine Regelmäßigkeit ist keine Prüfung. Ein Generator ist versucht, trotzdem eine hinzuschreiben — ".int()" bei den IDs, "z.email()" bei den Adressen, ein Literal bei der Rolle —, und dieser tut es nie, denn ein Beispiel zeigt, was Ihre Daten enthalten können, und nie, was sie enthalten müssen. Ein Typ, der enger ist als Ihre Daten, kostet einen Kompilierfehler auf Ihrem eigenen Rechner. Ein Schema, das enger ist als Ihre Daten, kostet eine abgelehnte Anfrage in der Produktion: die erste Rolle, die nicht admin ist, die erste ID 7.5, die erste Adresse, die das Muster nicht vorhergesehen hat.
Die Prüfungen würden sich außerdem unter Ihnen verschieben. Das "z.uuid()" von Zod 4 lehnt Zeichenketten in UUID-Form ab, die das ".uuid()" von Zod 3 akzeptiert hat, weil es die Variant-Bits prüft, und das ".int()" von Zod 4 lehnt eine ganze Zahl außerhalb des sicheren Bereichs ab, die das von Zod 3 durchgelassen hat — eine aus dem heutigen Beispiel geratene Prüfung wäre also eine andere Prüfung, je nachdem, welches Zod Sie installieren. Nichts, was das Beispiel bloß nahelegt, wird demnach in das Schema geschrieben. Wo es beim Eintreffen der Daten darauf ankommt, sagt Ihnen die Seite das stattdessen in einem Hinweis, und die Änderung bleibt Ihnen überlassen.
Was das Schema nicht von sich aus sagt
Unter dem Schema führt die Seite auf, was ihr aufgefallen ist und was sie nicht hineingeschrieben hat: einen Hinweis für jede unten aufgeführte Art, die zutrifft, einmal angegeben mit jeder Position, für die er gilt. Eine Position wird so geschrieben, wie die Ausgabe Dinge benennt — "Customer.phone" für einen Schlüssel, "Root.tags[]" für die Elemente eines Arrays, der Schlüssel in Anführungszeichen und eckigen Klammern, wo er kein Bezeichner ist, und für "__proto__" —, sodass sie sich im Schema auf einen Blick finden lässt. Das Beispiel, das mit der Seite geladen wird, zeigt jede Art außer der zu Infinity.
- Nur null. Das Beispiel enthielt an dieser Position nie etwas anderes als null, also sagt das Schema "z.null()" und lehnt den ersten echten Wert ab. Entscheiden Sie, was das Feld enthält, wenn es ausgefüllt ist, und schreiben Sie das selbst — etwa "z.string().nullable()" —, oder fügen Sie ein Beispiel ein, in dem es einen Wert hat.
- Infinity. Eine Zahl, die das übersteigt, was eine JavaScript-Zahl fassen kann, wie 1e999, wird beim Parsen des JSON zu Infinity oder -Infinity. Das "z.number()" von Zod 4 lehnt eine unendliche Zahl ab und das von Zod 3 akzeptiert sie, also ist das unter Zod 4 der eine Fall, in dem ein Schema genau das Beispiel ablehnt, aus dem es gemacht wurde. Die Frage, die das aufwirft, betrifft die Daten und nicht das Schema: Die Ziffern gehen verloren, bevor irgendein Validator sie sieht, also ist es Sache dessen, was diesen Wert geschrieben hat, zu beantworten, ob er überhaupt eine Zahl sein sollte.
- Ganze Zahlen. Jede Zahl an dieser Position war ganzzahlig, und "z.number()" akzeptiert auch 7.5. Wo ein Wert ganzzahlig sein muss — eine ID, eine Anzahl, eine Stückzahl —, fügen Sie ".int()" selbst hinzu; wo es ein Preis ist, der zufällig rund war, lassen Sie ihn, wie er ist. Der Hinweis entfällt für jede Position, die eine ganze Zahl außerhalb des sicheren Bereichs enthält, von "Number.MIN_SAFE_INTEGER" bis "Number.MAX_SAFE_INTEGER", weil das ".int()" von Zod 4 solche ablehnt, und ein Rat, der ein Schema sein eigenes Beispiel ablehnen ließe, ist die eine Art von Rat, die die Seite nicht gibt.
- Nichts abzuleiten. Die Elemente eines leeren Arrays, ein Objekt ohne Schlüssel und ein Wert, der mehr als 100 Ebenen tief verschachtelt ist, geben der Inferenz nichts, woran sie sich halten kann, also prüft das Schema nicht, was dort vorliegt: "z.unknown()" akzeptiert überhaupt jeden Wert und "z.record(z.string(), z.unknown())" jedes Objekt. Fügen Sie ein Beispiel ein, in dem dieses Array Elemente und dieses Objekt Schlüssel hat, oder schreiben Sie diesen Teil des Schemas von Hand.
Ein Hinweis ändert nie ein Byte des Schemas. Er ist ein Satz daneben, in der Sprache der Seite, und ob Sie die Änderung, auf die er zeigt, vornehmen oder auslassen, liegt bei Ihnen. Es gibt keinen Hinweis zu Zeichenkettenformaten, Literalen oder Enums: Jeder davon würde eine Regel raten, die das Beispiel nicht zeigen kann.
Schlüssel, die das Schema nicht aufführt, werden verworfen
"z.object" lässt ein Objekt durch, das Schlüssel trägt, die es nicht aufführt, und nimmt sie nicht in das auf, was es zurückgibt. Das ist die Voreinstellung von Zod in beiden Versionen und das Leiseste, was ein Schema tut: Ein Feld, das Ihrem Beispiel zufällig fehlte, verschwindet aus den Daten, die Ihr Code erhält, ohne dass ein Fehler es meldet. Deshalb erwähnt es der Satz unter jedem Schema auf dieser Seite.
Die Seite wählt nicht für Sie zwischen strikt und lose, weil jedes von beiden mehr behauptet, als ein Beispiel zeigen kann. Ein striktes Objekt lehnt jeden Schlüssel ab, den es nicht aufführt — keine Schlüssel außer diesen —, und kein Beispiel kann das von der nächsten Nutzlast beweisen. Ein loses Objekt behält zusätzliche Schlüssel, und sein abgeleiteter Typ erhält für sie eine Indexsignatur, sodass es nicht mehr die Antwort der TypeScript-Seite wäre. Das Verwerfen ist das eine Verhalten, das nimmt, was der TypeScript-Typ nimmt, und trotzdem diesen Typ ableitet — ein Wert mit zusätzlichen Eigenschaften erfüllt auch ein Interface. Um anders zu wählen, ändern Sie das Schema von Hand:
- Um Schlüssel abzulehnen, die das Schema nicht aufführt, schreiben Sie unter Zod 4 "z.strictObject", wo die Ausgabe "z.object" schreibt, oder hängen Sie unter Zod 3 ".strict()" an das "z.object" an.
- Um sie zu behalten, schreiben Sie unter Zod 4 "z.looseObject" oder hängen Sie unter Zod 3 ".passthrough()" an. Zod 4 führt diese beiden Zod-3-Methoden weiterhin aus und bezeichnet sie als Legacy.
Jedes Objekt in der Ausgabe ist ein eigenes Schema, also wird die Wahl Objekt für Objekt getroffen: Die Wurzel strikt zu machen, ändert nichts an den Objekten, die in ihr verschachtelt sind, was oft das ist, was Sie wollen, wenn Sie nur auf der äußeren Hülle bestehen dürfen.
Ein Name für jedes Schema und seinen Typ
Auf jedes Schema folgt sein Typ — "export type Customer = z.infer<typeof Customer>" direkt nach "export const Customer" —, so wie zod.dev seine eigenen Beispiele schreibt: ein Name für den Wert, der die Daten prüft, und für den Typ, den er erzeugt, da TypeScript Werte und Typen in getrennten Namensräumen hält. Dieser Typ ist derjenige, den die Seite JSON zu TypeScript für dasselbe JSON ausgibt, Name für Name und Schlüssel für Schlüssel, mit denselben optionalen Schlüsseln, denselben Unions und null an denselben Stellen — bis auf die unten genannten Ausnahmen.
Das Repository prüft dieses Versprechen, statt ihm zu vertrauen: Ein Korpus von Beispielen durchläuft beide Seiten und dann den TypeScript-Compiler mit jeder Version von Zod, der für jeden Namen gefragt wird, ob die beiden Typen identisch sind und ob jeder dem anderen zuweisbar ist. Wo er anders antwortet, liegt die Stelle tief in einem Dokument. Jenseits der Tiefengrenze, wo ein Schlüssel "z.unknown()" enthält, leitet Zod 3 diesen Schlüssel als optional ab, während die TypeScript-Seite ihn erforderlich macht. Und unter Zod 4 gibt der Compiler beim Typ eines Arrays auf, das Dutzende Ebenen tief verschachtelt ist, und meldet den Fehler TS2589 in der eigenen Zeile des Typs; das Schema über dieser Zeile läuft weiterhin und akzeptiert weiterhin das Beispiel, und nur der abgeleitete Typ geht verloren.
Beide Vergleiche lesen einen optionalen Schlüssel so, wie TypeScript es standardmäßig tut. Unter "exactOptionalPropertyTypes", das ausgeschaltet ist, sofern ein Projekt es nicht einschaltet, lässt das "z.infer" eines optionalen Schlüssels auch ein explizites undefined zu, unter jeder der beiden Zod-Versionen, wo der Typ der TypeScript-Seite das nicht tut.
Für Zod 4 geschrieben und unter Zod 3 weiterhin gültig
Die Ausgabe hält sich an das, was beide Hauptversionen haben — "z.object", "z.array", "z.union" über zwei oder mehr Mitglieder, "z.string()", "z.number()", "z.boolean()", "z.null()", "z.unknown()", "z.record(z.string(), z.unknown())", ".optional()", ".nullable()" und "z.infer" —, unter der Importzeile, mit der die eigenen Beispiele von zod.dev beginnen. Nichts darin kam mit Zod 4 hinzu, und nichts darin ist dort veraltet, sodass ein Projekt, das bei Zod 3 geblieben ist, sie so einfügen kann, wie sie ist.
Derselbe Text verhält sich auf beiden allerdings nicht gleich, und jeder Unterschied wird in diesem Leitfaden dort genannt, wo er zählt. Das "z.number()" von Zod 4 lehnt eine unendliche Zahl ab, wo das von Zod 3 sie akzeptiert, und das ist der Infinity-Hinweis. Ein Schlüssel, dessen Wert "z.unknown()" ist, ist im abgeleiteten Typ von Zod 3 optional und in dem von Zod 4 erforderlich — und ab Zod 4.4 auch beim Parsen der Daten erforderlich —, und einen solchen Schlüssel schreibt diese Ausgabe immer nur jenseits der Tiefengrenze. Zod 3 prüft einen Schlüssel namens "__proto__", und Zod 4 tut es nicht. Und der Compiler gibt beim Typ von Zod 4 für ein Array auf, das Dutzende Ebenen tief verschachtelt ist, wo er den von Zod 3 ermittelt.
Die Ausgabe ist für das reguläre Zod geschrieben, mit Methoden: "z.string().nullable().optional()". Zod Mini schreibt dasselbe Schema stattdessen mit Funktionen, "z.optional(z.nullable(z.string()))", sodass Zod Mini die Ausgabe in ihrer jetzigen Form nicht ausführen kann.
Warum die Wurzel zuletzt kommt
Die TypeScript-Seite gibt die Wurzel zuerst aus und die Objekte, die sie verwendet, danach, da ein Typ vor der Zeile verwendet werden darf, die ihn deklariert. Ein Schema darf das nicht: Es ist ein Wert, und eine "const", die oberhalb ihrer eigenen Deklaration gelesen wird, wirft einen ReferenceError, während das Modul noch geladen wird. Also kommt hier jedes Schema nach jedem Schema, das es verwendet, und die Wurzel kommt zuletzt — im Beispiel zuerst Customer und LineItem, dann Root, das sie enthält —, weshalb die Wurzel, mit der die TypeScript-Seite beginnt, diese hier abschließt.
Eine solche Reihenfolge gibt es immer. Die Inferenz ist ein Baum, und jedes Objekt, das sie auslagert, wird von genau einer Stelle aus verwendet, also muss kein Schema auf sich selbst verweisen oder auf eines, das nach ihm ausgegeben wird, und die Ausgabe braucht nie "z.lazy".
Häufig gestellte Fragen
- Warum ist eine ID "z.number()" und nicht "z.number().int()"?
- Weil ein Beispiel zeigen kann, dass bisher jede ID ganzzahlig war, aber nicht, dass es die nächste sein wird. Die Seite sagt es stattdessen: Der Hinweis zu ganzen Zahlen führt jede Position auf, an der jede Zahl ganzzahlig war, und wo Sie wissen, dass ein Wert ganzzahlig bleiben muss, ist das Hinzufügen von ".int()" eine Änderung von einem Wort. Der Hinweis entfällt, wo eine Zahl eine ganze Zahl außerhalb des sicheren Bereichs ist, da das ".int()" von Zod 4 dieses Beispiel selbst ablehnen würde.
- Warum kommt eine E-Mail-Adresse als schlichtes "z.string()" heraus?
- Weil eine Zeichenkette, die in Ihrem Beispiel wie eine E-Mail-Adresse aussieht, nichts über die nächste sagt, und eine Formatprüfung, die stimmt, den eigenen Mustern von Zod folgen muss, die sich zwischen den Versionen ändern — das "z.uuid()" von Zod 4 lehnt bereits Zeichenketten ab, die das ".uuid()" von Zod 3 durchgelassen hat. Datumsangaben, URLs und UUIDs bleiben aus demselben Grund Zeichenketten, und ein Feld, das immer nur ein paar Werte enthielt, wird nie zu einem Enum oder einem Literal. Wenn Sie die Regel kennen, schreiben Sie sie hinein; das Schema ist gewöhnlicher Code, der Ihnen gehört.
- Warum scheitert mein eigenes Beispiel unter Zod 4?
- Es enthält eine Zahl, die das übersteigt, was eine JavaScript-Zahl fassen kann — sagen wir 1e999 —, und die beim Parsen des JSON zu Infinity oder -Infinity wurde, und das "z.number()" von Zod 4 lehnt eine unendliche Zahl ab, wo das von Zod 3 sie akzeptiert. Der Infinity-Hinweis nennt jede betroffene Position. Davon abgesehen akzeptiert ein Schema immer das Beispiel, aus dem es entstanden ist, da jeder Wert des Beispiels in das Schema eingeflossen ist, und das Repository prüft das unter beiden Versionen an einem Korpus von Beispielen.
- Warum ist ein Feld, das ich gesendet habe, aus dem geparsten Ergebnis verschwunden?
- Weil das Schema es nicht aufführt. "z.object" akzeptiert ein Objekt mit zusätzlichen Schlüsseln und gibt es ohne sie zurück, unter jeder der beiden Versionen, und ein Schlüssel, der Ihrem Beispiel fehlte, ist ein Schlüssel, den das Schema nie kennengelernt hat. Fügen Sie ihn dem Schema hinzu oder machen Sie dieses eine Objekt lose — "z.looseObject" unter Zod 4, ".passthrough()" unter Zod 3 —, wenn unbekannte Schlüssel unverändert durchkommen sollen.
- Ich habe ein Schema unter ein anderes verschoben und einen ReferenceError bekommen. Warum?
- Weil ein Schema ein Wert ist und JavaScript nicht zulässt, dass ein Wert oberhalb der Zeile gelesen wird, die ihn definiert. Die Seite gibt jedes Schema nach jedem Schema aus, das es verwendet, mit der Wurzel zuletzt, aus genau diesem Grund; halten Sie ein Schema oberhalb von allem, was darauf verweist, und der Fehler verschwindet.
- Warum heißt das Schema Customer und nicht CustomerSchema?
- Das ist die eigene Konvention von zod.dev: Ein Schema und der Typ, den es ableitet, teilen sich einen Namen, was TypeScript erlaubt, weil Werte und Typen in getrennten Namensräumen leben. Der Name selbst ist derjenige, den die Seite JSON zu TypeScript demselben Objekt gibt, also ist es auf beiden Seiten dasselbe Wort, für das Schema wie für seinen Typ.
- Für welche Version von Zod ist die Ausgabe geschrieben?
- Für Zod 4, ohne etwas zu verwenden, das Zod 3 fehlt, sodass sie auf jeder der beiden unverändert läuft. Die beiden unterscheiden sich an ein paar Stellen — eine unendliche Zahl, ein Schlüssel jenseits der Tiefengrenze, ein Schlüssel namens "__proto__" und der Typ eines sehr tief verschachtelten Arrays —, und jede davon wird oben erklärt. Es ist reguläres Zod mit verketteten Methoden; Zod Mini schreibt dasselbe Schema mit Funktionen und kann sie in ihrer jetzigen Form nicht ausführen.
- Kann ich echte Daten einfügen, samt Zugangsdaten?
- Ja. Das Schema wird in Ihrem Browser ermittelt: Was Sie einfügen, wird auf Ihrem eigenen Rechner gelesen und weder an einen Server gesendet noch gespeichert oder protokolliert. Das Schema enthält außerdem keinen Ihrer Werte, nur Ihre Schlüssel und die Art von Wert unter jedem, sodass ein Token im Beispiel als "z.string()" herauskommt und als nichts weiter.
Verwandte Werkzeuge
- JSON zu TypeScript
Ein Schema von dieser Seite läuft als Teil Ihres Codes und prüft die Daten bei jedem Eintreffen. Jene Seite gibt für dasselbe JSON dieselbe Antwort unter denselben Namen als reine TypeScript-Typen aus; diese werden beim Kompilieren Ihres Codes geprüft und fügen zur Laufzeit nichts hinzu.
- JSON zu Go
Diese Seite überlässt es Ihnen, ganze Zahlen zu verlangen, wo jede Zahl in Ihrem Beispiel ganzzahlig war. Jene Seite ermittelt dieselbe Form unter denselben Typnamen, doch Go kennt keinen Typ, der einfach eine JSON-Zahl ist, also gibt sie einem Feld, in dem jede Zahl als ganze Zahl geschrieben ist, einen Ganzzahltyp, und ein Wert mit Dezimalpunkt lässt sich dort nicht dekodieren.
- JSONPath-Tester
JSONPath-Abfragen (RFC 9535) gegen JSON testen.
- Markdown-Tabellen-Generator
Markdown-Tabellen aus CSV, TSV oder JSON bauen und ausrichten.