Zodで空文字をチェックするなら .nonempty()、もうmin(1) はやめましょう
Zodで入力必須チェックをするとき、z.string().min(1, '〜')
を使う例をよく見かけます。
でも、実は nonempty()
というより直感的なメソッドもあります。
💡 nonempty()
はZodの正式なメソッド
例えばこういう定義:
productCode: z.string().nonempty('商品コードは必須項目です')
これは以下と同じ意味です:
productCode: z.string().min(1, '商品コードは必須項目です')
Zodの公式ドキュメント(https://zod.dev/?id=strings)にはなぜか記載されていませんが、**ソースコードを見るとちゃんと存在しています**。
nonempty(message?: errorUtil.ErrMessage): ZodString;
入力値のチェックであれば空文字チェックだけで良いので、後で述べるnull,undefinedの考慮は要りません。
✅ 最小限のテストケース:空文字のチェック
it('should fail when productCode is missing', () => {
const invalidData = { ...baseValidData, productCode: '' }
const result = productSchema.safeParse(invalidData)
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.format().productCode?._errors).toContain(
'商品コードは必須項目です'
)
}
})
✅ テスト結果(min(1)
/ nonempty
どちらも同じ)
PASS src/app/product/types/__tests__/product-scheme.test.ts
productSchema - productCode
✓ should fail when productCode is missing (24 ms)
🤔 では undefined
や null
のときは?
次に undefined
や null
を渡してみると…
it('should fail when productCode is undefined', () => {
const invalidData = { ...baseValidData, productCode: undefined as any }
const result = productSchema.safeParse(invalidData)
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.format().productCode?._errors).toContain(
'商品コードは必須項目です'
)
}
})
it('should fail when productCode is null', () => {
const invalidData = { ...baseValidData, productCode: null as any }
const result = productSchema.safeParse(invalidData)
expect(result.success).toBe(false)
if (!result.success) {
const errors = result.error.format().productCode?._errors
expect(
errors?.some(e => e.includes('必須')) || errors?.some(e => e.includes('string'))
).toBe(true)
}
})
❌ テスト失敗(undefined
のとき)
FAIL src/app/product/types/__tests__/product-scheme.test.ts
productSchema - productCode
✓ should fail when productCode is missing
✕ should fail when productCode is undefined
✓ should fail when productCode is null
● should fail when productCode is undefined
Expected value: "商品コードは必須項目です"
Received array: ["Required"]
📌 なぜ undefined
だけ違うエラー?
Zodでは、.string()
の型バリデーションが先に走ります。
キーが存在しない(undefined
)と「Missing value」とみなされ、Zodのデフォルトメッセージ "Required"
が返ってきます。
一方で .min(1)
や .nonempty()
は「空文字に対する長さチェック」なので、そもそも undefined
には到達しません。
✅ 対策:required_error
を指定する
productCode: z
.string({ required_error: '商品コードは必須項目です' })
.nonempty('商品コードは必須項目です')
これで、undefined
のときも空文字のときも、同じメッセージで統一されます。
🔁 テスト再実行結果:
PASS src/app/product/types/__tests__/product-scheme.test.ts
productSchema - productCode
✓ should fail when productCode is missing
✓ should fail when productCode is undefined
✓ should fail when productCode is null
✅ まとめ
入力値 |
.min(1) / .nonempty()
|
対応策 |
---|---|---|
'' |
❌ エラー(OK) | メッセージ通る |
null |
❌ 型エラー |
nullable() で対応も可 |
undefined |
❌ "Required" のまま |
required_error で統一しよう |
✅ おまけ:どちらを使うべき?
- 明示的に「空文字禁止」を書きたいなら
nonempty()
の方が読みやすい - より一般的なパターンや制約付き(例:
min(5)
,max(10)
)があるならmin(x)
の方が柔軟 - チームで統一しておくのがベスト
🔚 おわりに
Zodの .nonempty()
は便利なのに知られていない…!
これを機に min(1)
だけでなく nonempty()
の存在も知っておくと、コードがより意図的で読みやすくなります。