前回の記事に引き続き今回の記事もZodについての記事になります。今回はZodをフォームのバリデーションなどに利用する際に必要になるエラーハンドリング関連の機能について調査した内容を書いていきます。
本記事を執筆時点でのZodのバージョンはv3.21.4です。
Zodはスキーマのparseメソッドを呼び出すことでバリデーションを行うことができますが、バリデーションの結果として入力に不備があればこのメソッドはエラー内容を情報として含んだ例外を投げます。
import { z, ZodError } from "zod"
const user = z.object({
name: z.object({
first: z.string().min(1),
last: z.string().min(1),
}),
email: z.string().email(),
})
try {
const parsed = user.parse({ name: "", email: "" })
} catch (e) {
if (e instanceof ZodError) {
// ここでエラーのハンドリングを行う
}
}
上記のコードを実行するとparse
実行時に次の様なエラーオブジェクトが例外として投げられました。
{
issues: [
{
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "String must contain at least 1 character(s)",
"path": [
"name",
"first"
]
},
{
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "String must contain at least 1 character(s)",
"path": [
"name",
"last"
]
},
{
"validation": "email",
"code": "invalid_string",
"message": "Invalid email",
"path": [
"email"
]
}
]
}
ドキュメントによるとバリデーションの失敗時に投げられるエラーオブジェクトはZodError
でありZodError
はエラーの詳細情報をZodIssue
の配列で保持していることがわかります。
エラーの発生箇所はZodIssue
のプロパティであるpath
にスキーマオブジェクトの当該箇所へのパスとして保存されています。エラーの情報をZodIssue
の配列としてではなくて構造化されたオブジェクトで受け取りたい場合があると思います。そういった場合には自分でオブジェクトをパースすることも可能ですがZodError
のformat
メソッドを利用することもできます。上記ZodError
をformat
メソッドで変換したところ以下のようなオブジェクトが得られました。
{
_errors: [],
name: {
_errors: [],
first: { _errors: [ 'String must contain at least 1 character(s)' ] },
last: { _errors: [ 'String must contain at least 1 character(s)' ] }
},
email: { _errors: [ 'Invalid email' ] }
}
format
メソッド以外にもflatten
メソッドを使うこともできます。こちらはfieldErrors
内に各バリデーション結果が展開されますが、name.first
とname.last
のようなネスト下のプロパティの情報はルート直下プロパティであるname
に配列で保存されておりfirst
とlast
というパスの情報は出力に現れなくなりました。
{
formErrors: [],
fieldErrors: {
name: [
'String must contain at least 1 character(s)',
'String must contain at least 1 character(s)'
],
email: [ 'Invalid email' ]
}
}
ここまでエラーメッセージはデフォルトのものを利用していましたが、このメッセージはZodライブラリ内部のdefaultErrorMap
に定義されており、エラーの内容によって表示するメッセージが細かく定義されています。このエラーマップを変更することで表示されるエラーメッセージを変更することができます。
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
if (issue.code === z.ZodIssueCode.too_small) {
if (issue.type === "string") {
return { message: `${issue.minimum}文字${issue.inclusive ? "以上で" : "より多く"}なければいけません` }
}
}
return { message: ctx.defaultError }
}
z.setErrorMap(customErrorMap)
これによりparse
実行後のエラー内容は以下のように変化しました。
[
{
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "1文字以上でなければいけません",
"path": [
"name",
"first"
]
},
{
"code": "too_small",
"minimum": 1,
"type": "string",
"inclusive": true,
"exact": false,
"message": "1文字以上でなければいけません",
"path": [
"name",
"last"
]
},
{
"validation": "email",
"code": "invalid_string",
"message": "Invalid email",
"path": [
"email"
]
}
]
上の方法ではエラーメッセージのグローバルな定義を上書きするため他のスキーマのバリデーションにも影響を与えることになりますが、スキーマ内でのみエラーメッセージを変更する方法ややパース時にカスタムしたエラーマップを渡す方法があります。
// スキーマ毎にエラーマップを上書きする
const schema: z.string({ errorMap: customErrorMap })
// パース時にエラーマップを渡す
schema.parse(input, { errorMap: customErrorMap })
以上の機能を利用することでZodでのエラーハンドリングを不自由なく実行することができます。