JSONからZodへ
JSONからZodへは、ブラウザ内でサンプルJSONを実行可能なZodスキーマに変換します。TypeScriptのインターフェースと違い、出力はそのまま動くコードです。ランタイムで値をパースし、サンプルに含まれていなかった値は拒否されます。optionalやnullableのフィールドは推測ではなく推論で判定されるよう、複数のレコードを貼り付けてください。
ドキュメントが途中で終わっています。切れている可能性があります。
^
JSONからZodへはどう動きますか?
TypeScriptのインターフェースとZodスキーマは似て見えますが、まったく別の仕事をします。インターフェースはコンパイル時に消去されます。エディタには形状を伝えますが、実行されるプログラムには何も伝えません。Zodスキーマはランタイムまで生き残る値であり、実際のレスポンスをパースして拒否できます。APIが約束を守ったと願うことと、守ったことを確認することの違いです。
同じ推論、異なる出力
このツールは型推論を隣の5つの言語ジェネレータと共有しています。サンプルを一度走査するだけで、どのフィールドがoptionalか、どれがnullableか、どのオブジェクト形状が同一か、混合配列の要素型は何かが決まります。異なるのは出力だけです。判断こそが難しい部分であり、実装を2つ作れば遅かれ早かれ食い違うため、これは重要です。
optionalとnullableは別物
{"a": null}と{}は異なるドキュメントです。Zodも両者を異なる書き方で表します。前者には.nullable()、後者には.optional()であり、どちらも他方を含意しません。存在しないこともnullであることもあるキーには、この順序で両方が必要です。つまりz.string().optional().nullable()です。ここを誤ると、生成された検証器がフィクスチャには合格し、本番2週目で失敗する典型的な原因になります。
形式チェックはすべての値への主張
推論は文字列がUUID、メール、日時のどれかに見えると検知し、素のz.string()の代わりにz.uuid()、z.email()、z.iso.datetime()を出力できます。これは実用上とても有用で、同時に多少危険です。そのフィールドが今後持ちうるすべての値についての主張であり、1サンプルから推論されているためです。テストフィクスチャではUUIDで本番ではスラグの列があると、検証器は実データを拒否します。それゆえトグルになっており、どの検出形式がどの検証器に対応するかは下のリファレンス表に載っています。
形式の書き分けはZod 3とZod 4で異なる
Zod 4は文字列形式の検証器をトップレベルに移動しました。z.uuid()、z.email()、z.url()、z.iso.datetime()であり、非推奨となったz.string().uuid()系を置き換えます。どちらも動作するため、対象バージョンは推測ではなく選択です。デフォルトがZod 4なのは、今日生成するスキーマが今日始まるコードベースに入るものだからです。
JSONサンプル
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Zodスキーマ
export const RootSchema = z.object({
id: z.uuid(),
email: z.email(),
score: z.number().int(),
deleted_at: z.unknown(),
tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;JSONサンプル → Zodスキーマ
JSONサンプル
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Zodスキーマ
import { z } from "zod";
export const RootSchema = z.object({
id: z.uuid(),
email: z.email(),
score: z.number().int(),
deleted_at: z.unknown(),
tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;
JSONからZodへが対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| Zodバージョン | Zod 3 | Zod 4 | Zod 4 | Zod 4はトップレベルのz.uuid()、z.email()、z.url()、z.iso.datetime()を使用します。Zod 3はz.string().uuid()系を使用し、Zod 4でも受け付けますが非推奨として扱われます。出力のそれ以外の部分は同一です。 |
| strictオブジェクト | 真偽値 | オフ | .strict()を追加します。スキーマが知らないキーは黙って無視される代わりにエラーになります。検証対象のリクエストボディに対してはオンにする価値があり、通知なしにフィールドを追加してくるサードパーティのレスポンスでは通常は誤りです。 |
| z.infer型出力 | 真偽値 | オン | 各スキーマの隣にexport type X = z.infer<typeof XSchema>を出力します。TypeScriptの型とランタイムの検証器がずれることがなくなるためです。Zodが手書きのインターフェースより明確に優れている部分です。 |
| 形式チェック | 真偽値 | オン | サンプルがその形式に見える箇所にz.uuid()、z.email()、z.url()、z.iso.datetime()、z.iso.date()を適用します。1サンプルからの推論は強い主張です。本番環境で列の中身が均質でない場合はオフにしてください。 |
| オプショナリティ | 推論 | 複数レコードから | 同じ形状のレコードのうち少なくとも1つで欠けているキーには.optional()が付きます。複数のレコードを貼り付けてください。1レコードではこれを表現できず、1レコードから作ったスキーマは2つ目のレコードを拒否します。 |
| Nullable | 推論 | .nullable() | あるレコードではnull、別のレコードでは文字列である値はz.string().nullable()になります。キーが存在しないこともnullであることもある場合は.optional().nullable()となり、これは2つの異なる要件であって冗長ではありません。 |
| 整数 | 推論 | z.number().int() | 小数部のないJSON数値は整数スキーマになり、サンプルが整数だった箇所にfloatが届くと、黙って受け入れられる代わりに拒否されます。あるレコードでは1、別のレコードでは1.5であるフィールドは通常のz.number()になります。 |
| ルート配列 | ドキュメントの形状 | z.array(RootSchema) | トップレベルがレコード配列の場合、要素のスキーマに加えてRootSchemaラッパーを出力します。ドキュメント自体のスキーマが1行になり、自分で組み立てる必要がありません。 |
| 入力サイズ | バイト | 2 MB | 推論はすべての値を読み取ります。数百の代表的なレコードは100万レコードと同等に形状を記述できるため、この上限が実用上の損失になることはありません。 |
よくある質問
TypeScriptのインターフェースではなくZodスキーマを生成するのはなぜですか?
TypeScriptの型はコンパイル時に消去されるためです。エディタには形状を伝えますが、実行されるプログラムには何も伝えません。そのため型に違反するレスポンスも素通りします。Zodスキーマはランタイムで動く実体の値であり、safeParseは型付きのデータか、失敗した正確なパスを返します。APIが自作で十分にテストされているならインターフェースで足ります。他人のAPIやユーザー入力なら、実際に検査するのは検証器だけです。
APIが常に送信しているフィールドに.optional()が付くのはなぜですか?
サンプルの中の少なくとも1レコードにそのフィールドがなかったからです。ジェネレータは与えられたデータしか記述できません。常に現れるサンプルを貼り付ければ必須のスキーマになりますし、.optional()を手で削除しても構いません。サンプルより契約をよく知っているなら、それが正しい判断です。なお、これは.nullable()とは逆の失敗です。存在しないキーとnullの値は別物で、スキーマは両者を区別します。
.nullable()と.optional()は同じですか?
いいえ。両者を混同するのが生成スキーマで最も多いバグです。{a: null}はキーが存在して値がnullであり、{}はキー自体が存在しません。.nullable()は前者を、.optional()は後者を受け入れ、どちらもありうるフィールドには両方が必要です。つまりz.string().optional().nullable()です。順序は挙動ではなく可読性に関係しますが、Zod自身のドキュメントもoptionalを先に書きます。
普通の文字列がz.uuid()やz.email()になったのはなぜですか?
推論がその位置で見たすべての値がパターンに一致したため、形式を型の一部として扱いました。大抵は正しく、ときどき誤りです。フィクスチャではUUID、本番ではスラグを格納する列は、実データで検証に失敗するようになります。形式チェックをオフにすればすべて素のz.string()になり、該当行だけ編集しても構いません。どの形式がどの検証器に対応するかは、リファレンス表に正確に載っています。
どちらのZodバージョンを対象にすべきですか?
Zod 3に固定されているのでなければZod 4です。Zod 4は形式検証をトップレベルに移動し(z.string().uuid()の代わりにz.uuid())、旧来の書き方を非推奨にしました。そのためZod 4向けに生成したスキーマは新しいコードベースで非推奨警告を出しません。旧来の呼び出しはZod 4でも動くため、Zod 4プロジェクトに貼り付けたZod 3スキーマは壊れてはおらず、古いだけです。
JSONはどこかにアップロードされますか?
いいえ。推論も描画も、すでに開いているタブの中で実行されます。DevToolsを開いてNetworkパネルを見ながらドキュメントを貼り付ければ確かめられます。検証器に変換されるドキュメントは本番APIのレスポンスであり、実際の顧客レコードが含まれていることも珍しくないため、この点は多くのツール以上に重要です。