【Zod】ErrorMapでエラーメッセージをカスタマイズして共通化する

const userSchema = z.object({
id: z.string().regex(/^[a-z0-9]+$/, { message: '不正なIDです' }),
name: z
.string()
.min(1, { message: '未入力です' })
.max(20, { message: '20文字以内で入力してください' }),
bio: z
.string()
.min(20, { message: '20文字以上で入力してください' })
.max(255, { message: '255文字以内で入力してください' }),
email: z.string().email({ message: '不正なメールアドレスです' }),
});
  • Zodでエラーメッセージを設定する際、上記のようにスキーマ毎にmessageを指定する方法が一般的かもしれない
  • しかしこの方法だとスキーマ定義のコード量が増えるだけでなく設定ミスなども発生しやすくなるので、いい感じにエラーメッセージを共通化したい
  • デフォルトのメッセージは英語なので日本語化したい

ZodErrorMapを使う方法

const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
switch (issue.code) {
case z.ZodIssueCode.too_big:
if (issue.type === 'string') {
return { message: `${issue.maximum}文字以内で入力してください` };
}
break;
case z.ZodIssueCode.too_small:
if (issue.type === 'string') {
if (issue.minimum === 1) return { message: '未入力です' };
return { message: `${issue.minimum}文字以上で入力してください` };
}
}
return { message: ctx.defaultError };
};
z.setErrorMap(customErrorMap);
  • ZodErrorMapでエラー内容に応じた任意のメッセージを設定
  • z.setErrorMapすることでグローバルにこの設定が反映される
z.string().min(1).safeParse('').error?.issues[0].message; // '未入力です'
z.string().max(255).safeParse('a'.repeat(256)).error?.issues[0].message; // '255文字以内で入力してください'

今回は入力値がstringのケースのみを考慮したErrorMapではあるが、これでスキーマ毎にmessageを指定する必要もなく、エラーメッセージを共通化できた。

z.string({ errorMap: customErrorMap });

スキーマ毎にErrorMapを指定することも可能。

const specificErrorMap: z.ZodErrorMap = (issue, ctx) => {
switch (issue.code) {
// some message settings
}
return { message: customErrorMap(issue, ctx).message };
};
z.string({ errorMap: specificErrorMap });

そのためグローバルに設定したErrorMapを継承しつつ、より具体的なErrorMapで上書きする、ということも可能。

superRefineを使う方法

const minRefinement =
(min: number): z.SuperRefinement<string> =>
(val, ctx) => {
if (val.length < min) {
ctx.addIssue({
code: z.ZodIssueCode.too_small,
minimum: min,
type: 'string',
inclusive: true,
message: `${min}文字以上で入力してください`,
});
}
};
const maxRefinement =
(max: number): z.SuperRefinement<string> =>
(val, ctx) => {
if (val.length > max) {
ctx.addIssue({
code: z.ZodIssueCode.too_big,
maximum: max,
type: 'string',
inclusive: true,
message: `${max}文字以内で入力してください`,
});
}
};
z.string().superRefine(minRefinement(1)).superRefine(maxRefinement(255));
  • superRefineに渡す関数を動的に生成し、エラーメッセージ (とロジック) を共通化する方法
  • 苦し紛れなので正直前述のErrorMapを使う方が無難
    • Zodから提供される検証ロジックを利用できない
    • superRefineを多用することになるのでスキーマの可読性が下がる