Zodは空文字やnullを通すのか — デフォルト挙動の早見表
この章の目次開く
Zodを書いていると、必ずこの手の疑問に突き当たります。
z.string()って、空文字""は通るんだっけ?nullとundefined、デフォルトで弾かれるのはどっちだっけ?.optional()と.nullable()、どっちがどっちだっけ?
この章はその都度開く前提の早見表です。表を見て解決したらそのまま閉じてもらって構いませんし、理由まで知りたければ表の下の解説を読んでください。
学習者z.string().min(1) を付けたのに空白だけの入力が通っちゃった…これ、仕様なの?
早見表1: そのまま通るか
素のスキーマに値を渡したときに、検証を通過するかどうかです(z.array() は z.array(z.string()) で確認しています)。
| 入力値 | z.string() | z.number() | z.boolean() | z.array() |
|---|---|---|---|---|
"" | 通る | ✗ | ✗ | ✗ |
" "(空白のみ) | 通る | ✗ | ✗ | ✗ |
"123" | 通る | ✗ | ✗ | ✗ |
0 | ✗ | 通る | ✗ | ✗ |
NaN | ✗ | ✗ | ✗ | ✗ |
Infinity | ✗ | ✗ | ✗ | ✗ |
true | ✗ | ✗ | 通る | ✗ |
[](空配列) | ✗ | ✗ | ✗ | 通る |
null | ✗ | ✗ | ✗ | ✗ |
undefined | ✗ | ✗ | ✗ | ✗ |
この表から押さえておきたいのは2点です。
空文字と空配列は「空だから」では弾かれない
z.string() は「文字列であるか」だけを見ます。長さ0の文字列も立派な文字列なので通ります。同じ理由で z.array() は空配列を通します。「必須項目なのに空で登録できてしまった」というバグの多くはこれが原因です。空を弾きたいなら、後述の早見表3のように明示的に書きます。
0 は通り、NaN は弾かれる
z.number() は 0 を通します。0 は正当な数値だからです。一方 NaN は、JavaScriptの typeof 上は "number" ですが、Zodは弾きます。Infinity も弾かれます。
日付は別枠で考える
z.date() は日付文字列を受け取らない
z.date() が通すのは Date オブジェクトだけです。"2026-01-01" のような文字列は弾かれます。JSONにDate型は存在しないので、APIレスポンスを検証するときに z.date() を書くとほぼ確実に落ちます。
| やりたいこと | 書き方 |
|---|---|
Date オブジェクトを検証する | z.date() |
"2026-01-01" 形式の文字列を検証する | z.iso.date() |
"2026-01-01T00:00:00Z" 形式を検証する | z.iso.datetime() |
文字列を受け取って Date に変換する | z.coerce.date() |
z.iso.date() はハイフン区切りのISO形式だけを通します。"2026/01/01" はスラッシュ区切りなので弾かれます。
早見表2: nullとundefinedの許し方
いちばん忘れやすいところです。まず前提として、null も undefined も、デフォルトでは両方とも弾かれます。「どちらかが特別扱いで通る」ということはありません。
違いが出るのは、許すときの書き方です。
| 書き方 | null を渡す | undefined を渡す | キー自体が無い |
|---|---|---|---|
| そのまま | ✗ | ✗ | ✗ |
.optional() | ✗ | 通る | 通る |
.nullable() | 通る | ✗ | ✗ |
.nullish() | 通る | 通る | 通る |
.default("D") | ✗ | "D" になる | "D" になる |
.catch("C") | "C" になる | "C" になる | "C" になる |
覚え方はそのままで、optional = undefined を許す、nullable = null を許す、nullish = 両方です。
先生.optional() を付けたのに null が来て落ちる、というのが実務で一番多い事故。APIやDBは「値が無い」を null で表すことが多いから、フロントの感覚で .optional() だけ付けると噛み合わないんだ。
「キーが無い」は undefined と同じ扱い
オブジェクトのキーそのものが存在しない場合、Zodはそれを undefined が渡されたのと同じように扱います。実際、必須キーが欠けているときのエラーは次のようになります。
z.object({ a: z.string() }).safeParse({});
// issues[0].message: "Invalid input: expected string, received undefined"{} にはキー a すらありませんが、メッセージは received undefined です。だからキーの省略を許したいときは .nullable() ではなく .optional() が正解になります。
.default() と .catch() は別物
どちらも「値が無いときに既定値を入れる」ように見えますが、動く条件がまったく違います。
| 既定値が入る条件 | |
|---|---|
.default(v) | 入力が undefined のときだけ |
.catch(v) | 検証に失敗したとき全部(型が違う、範囲外、null、なんでも) |
z.string().default('D').safeParse(null); // ✗ 失敗する
z.string().catch('C').safeParse(null); // 成功して 'C'
z.string().catch('C').safeParse(12345); // 成功して 'C'.default() には関数も渡せます。呼ばれるたびに評価されるので、現在時刻やランダム値の既定値に使えます。
z.date().default(() => new Date());早見表3: 「空」を弾きたいときの定型
| 弾きたいもの | 書き方 | 補足 |
|---|---|---|
空文字 "" | z.string().min(1) | 空白だけの " " は通ってしまう |
| 空白だけの入力 | z.string().trim().min(1) | trim してから長さを見るので両方弾ける |
空配列 [] | z.array(z.string()).min(1) | 「1件以上必須」の意味になる |
null / undefined | 何も付けない | デフォルトで弾かれる |
実行結果で並べると、.min(1) だけでは足りないことがはっきりします。
| スキーマ | "" | " " |
|---|---|---|
z.string() | 通る | 通る |
z.string().min(1) | ✗ | 通る |
z.string().trim().min(1) | ✗ | ✗ |
.trim().min(1) を使う、と覚えておくと事故が減ります。

よくあるハマりどころ
.optional() を付けたのに null で落ちる
APIやDBは「値なし」を null で返すことが多く、.optional() では受けられません。外部から来るデータで「値が無いこともある」項目は、.nullish() にしておくと両方受けられます。どちらが来るか決まっているなら、決まっている方だけを許す方が厳密です。
必須のはずのフォーム項目が空文字で登録できる
早見表1のとおり z.string() は "" を通します。HTMLのフォームは未入力の項目を空文字として送るため、未入力が「空文字という値」としてそのまま通過します。.trim().min(1) で塞ぎます。
数値の項目が 0 を弾いてしまう
逆のパターンです。「未入力なら弾く」つもりで .min(1) を書くと、正当な入力である 0 まで弾かれます。数量や個数のように 0 が意味を持つ項目では .min(0) と書くか、そもそも下限を指定しません。
z.coerce.number() を使ったら空欄が 0 になった
z.coerce.number() は変換してから検証するため、空文字も null もエラーにならず 0 になります(Number("") が 0 になるのと同じ理屈です)。フォームの空欄が0で保存される事故につながるので、変換を挟むときは「変換後の値」で早見表を引き直してください。
ちゃんと使うためのポイント
-
z.string()は空文字も空白のみの文字列も通す。必須入力は.trim().min(1) nullとundefinedは両方ともデフォルトで弾かれる。違うのは許し方optional= undefined、nullable= null、nullish= 両方- オブジェクトのキー欠落は
undefined扱いなので.optional()の担当 .default()はundefinedのときだけ、.catch()は失敗全部を握りつぶすz.date()は文字列を通さない。JSON由来の日付はz.iso.date()かz.coerce.date()
次の章では、ここまで単体で見てきたスキーマを組み合わせる z.objectでオブジェクトを検証する に進みます。定義していないキーが来たときにZodが黙って捨てている、という挙動もそこで扱います。
参考リンク
- Defining schemas - Zod — 各スキーマとメソッドの一覧(英語)
- Nullish coalescing operator - MDN —
nullとundefinedをまとめて「nullish」と呼ぶJavaScript側の背景