訂正記事: Zod の string.nonempty() は使わないでください(理由と置換レシピ)
この記事は、過去に「Zod の
nonempty()を推奨」と書いてしまった内容を訂正するものです。string フィールドはnonempty()ではなくmin(1)(必要ならtrim()併用)で扱うのが現在の実務的ベストプラクティスです。
結論(先に断定)
-
string に対する
.nonempty()は採用しない。
代わりにz.string().min(1)を使う。空白だけ(' ')も弾きたいならz.string().trim().min(1)。 -
配列/Set 向けの
.nonempty()は引き続き有効(z.array(T).nonempty()/z.set(T).nonempty()は使ってよい)。 - 一部のバージョンでは
string.nonempty()が実行できることがあるが、公式の説明・コミュニティ議論はmin(1)を推奨しており、事実上の非推奨運用と考えるのが安全。
背景と経緯(要点だけ)
- もともと「空文字を簡単に弾きたい」という要望から
string.nonempty()を求める声がありました(Issue #63)。 - その後、「
z.string()は“型が string か”しか見ない。空文字''は string として妥当」という設計が再確認され、
“必須”は長さ制約(min(1))で書く方針が広く共有されるようになりました。 - 実務(特に React Hook Form など)では 未入力既定値が
''になるケースが多く、trim().min(1)が最小公倍数として落ち着いています。 - 以降の議論では、空と最小文字数でメッセージを分ける、同じ記述をヘルパー化する、空白専用の規約を regex/refine で補うといった具体策に収束しています。
参考:
- Issue: Request: nonempty for string(要望とその後の議論)
https://github.com/colinhacks/zod/issues/63 - Docs: Array/Set の
.nonempty()の説明(string には記載なし)
https://zod.dev
置換レシピ(非推奨からの移行)
1) もっとも基本:必須(空文字禁止)
const name = z.string().min(1, { message: '必須です' });
2) 空白だけも禁止(' ' を弾く)
const name = z.string().trim().min(1, { message: '必須です' });
trim()は前後の空白を除去します。内部の連続空白まで潰す要件は別途検討。
3) 「空」と「最小文字数未満」で別メッセージを出す
const title = z
.string()
.refine((s) => s.trim() !== '', { message: '空です' })
.refine((s) => s.length >= 3, { message: '3文字以上で入力してください' });
4) 同じパターンを何度も書かない(ヘルパー化)
const requiredStr = (msg = '必須です') =>
z.string().trim().min(1, { message: msg });
const schema = z.object({
firstName: requiredStr(),
apiEndpoint: requiredStr('API_ENDPOINT は必須です'),
});
5) 配列/Set はこれまで通り
const tags = z.array(z.string()).nonempty('1件以上選択してください');
React Hook Form(RHF)との実務ポイント
- 多くのフォーム実装では 未入力既定値が
''(空文字)。
→ スキーマ側でtrim().min(1)を入れておくと安定。 - HTML の
required属性だけでは 空白入力を防げない。
→ スキーマで担保する(UI とバリデーションを分離)。
よくある勘違い・落とし穴
-
required_errorを付ければ空文字も弾ける
→ 弾けません。required_errorは型エラー用。min(1)が必要です。 -
ドキュメントに
.nonempty()がある=string でも使える
→ その説明は 配列/Set 向け。string はmin(1)を使うのが現在の前提。 -
空白だけを弾けない
→trim().min(1)を使うか、要件に応じてrefine/regexを追加。
最小の差分(before/after)
- const name = z.string().nonempty('必須です');
+ const name = z.string().min(1, { message: '必須です' });
- const name = z.string().nonempty('必須です'); // ' ' を素通りしうる
+ const name = z.string().trim().min(1, { message: '必須です' });
まとめ(訂正の趣旨)
-
string の“必須”は
min(1)、空白対策はtrim()併用。 -
.nonempty()は 配列/Set では有効だが、string では使わない。 - このポリシーで統一すると、フォーム実装(特に RHF)での不具合や認知負荷を減らせます。
参考リンク
- Issue #63: Request: nonempty for string(議論の起点・長期スレッド)
https://github.com/colinhacks/zod/issues/63 - Zod ドキュメント(配列/Set の
.nonempty()、文字列制約)
https://zod.dev
タイトル候補
- 訂正記事: Zod の
string.nonempty()は非推奨運用です(理由と置換レシピ) - Zod string の必須は
min(1)一択—.nonempty()を使わない理由 - 実務ガイド: 空文字/空白を確実に弾く Zod スキーマの書き方