23
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

zodのエラーメッセージをカスタマイズする

Last updated at Posted at 2022-12-31

フロントエンドのフォームのバリデーションとしてzodを利用しました。
エラーメッセージのカスタマイズ方法がなかなか見つからなかったので、記しておきます。

なお、使用しているzodのバージョンは以下です。

zod@3.19.1
zodとは

GitHub

定義したバリデーションスキーマからTypeScriptの型を生成してくれます。
これにより、型定義とバリデーション定義を一括管理できます:grinning:

// バリデーションスキーマ
const userSchema = zod.object({
  // テキストのバリデーション
  userId: zod
    .string()
    .regex(/^[a-zA-Z0-9]+$/)
    .min(5)
    .max(15),
  // チェックボックス(複数選択可)のバリデーション
  interest: zod.array(zod.string()).min(1),
  // チェックボックス(項目が1つだけ。値がtrue or false)のバリデーション
  // trueのみ許可
  confirm: zod.literal(true),
});

// バリデーションスキーマから型定義を作成
type userValues = zod.infer<typeof userSchema>;

// type UserValues = {
//   userId: string;
//   interest: string[];
//   confirm: true;
// }

zodのエラーメッセージをカスタマイズする

zodのバリデーションエラーメッセージはデフォルトで英語になっています。
そのため、日本語で出力できるようにエラーメッセージをカスタマイズします。

方法1: 引数として渡す

一番簡単な方法です。
スキーマまたは各バリデーションメソッドの引数として渡します。

(このやり方は知っている!でもこのやり方は嫌なんだ!って方は方法2を参考にしてください。)

userSchema.ts
import * as zod from 'zod';

// オブジェクトスキーマ
const userSchema = zod.object({
  userId: zod
    // stringスキーマのエラーメッセージ
    .string({ required_error: '必須項目です', invalid_type_error: '入力値に誤りがります' })
    // 以下バリデーションメソッドの追加引数としてエラーメッセージを指定
    .regex(/^[a-zA-Z0-9]+$/, { message: '半角英数字で入力してください' })
    .min(5, { message: '5文字以上で入力してください。' })
    .max(15, { message: '15文字以下で入力してください。' }),
  interest: zod
    .array(zod.string({ required_error: '必須項目です', invalid_type_error: '入力値に誤りがります' }))
    .min(1, { message: '1つ以上選択してください' }),
  // literalは引数でmessageを渡せない
  confirm: zod.literal(true),
});

中には引数でメッセージを渡せないものもあります。
そのため、全てのバリデーションメソッドでこの方法が利用できるわけではありません!

また、個別にメッセージを指定するのは不便です:pensive:

方法2: ZodErrorMapでエラーメッセージをカスタマイズする

以下の様にZodErrorMapを用いて、zodのエラーコードごとにメッセージをカスタマイズします。

customErrorMap.ts
import * as zod from 'zod';

/**
 * zodエラーメッセージ(日本語)
 */
const customErrorMap: zod.ZodErrorMap = (issue, ctx) => {
  // zodエラーコードごとにメッセージをカスタマイズする
  switch (issue.code) {
    // 型に誤り
    case zod.ZodIssueCode.invalid_type:
      // undefinedだった場合は未入力判定
      if (issue.received === zod.ZodParsedType.undefined) {
        return { message: '必須項目です' };
      } else {
        return { message: '値に誤りがあります' };
      }

    case zod.ZodIssueCode.too_big:
      return { message: `${issue.maximum}文字以内で入力してください` };

    case zod.ZodIssueCode.too_small:
      if (issue.type === 'array') {
        return { message: `${issue.minimum}つ以上チェックしてください` };
      }
      return { message: `${issue.minimum}文字以上で入力してください` };
  }

  // デフォルトのメッセージを返す
  return { message: ctx.defaultError };
};

上記条件を満たさないエラーの場合は、デフォルトのエラーメッセージを出力します。

独自のZodErrorMapを作成する時は、zodデフォルトメッセージをセットしているファイルが参考になります!

ZodErrorMap について

https://github.com/colinhacks/zod/blob/master/ERROR_HANDLING.md#customizing-errors-with-zoderrormap

  • { message: string } を返す必要がある
  • issueはどういったエラーであったかというデータからmessageを除外したエラー詳細情報
  • ctx.defaultはデフォルトのエラーマップによって生成されたエラーメッセージ
  • src/ZodError.tsで定義されたdefaultErrorMapの優先順位が最も低く、上書きされたメッセージ(カスタマイズされたエラーメッセージ)の方が優先順位が高い

カスタマイズしたZodErrorMapをグローバルに適用する

カスタムのZodErrorMapをグローバルに適用するには.setErrorMap()に指定するだけです。

// zodカスタムエラーメッセージをグローバルに適用
zod.setErrorMap(customErrorMap);

zodimportしているファイルであるならば.setErrorMap()を呼び出せます。
ただ、適用範囲がグローバルなので、どこで呼び出すのかはプロジェクトごとにルールを決めたほうが良いでしょう。

カスタマイズしたZodErrorMapを個別のスキーマにバインドする

グローバルに適用させるのはちょっと...という場合には、
カスタマイズしたZodErrorMapを個別のスキーマにバインドする(デフォルトエラーをカスタムエラーで上書きする)こともできます。

userSchema.ts
const userSchema = zod.object({
  userId: zod
+    .string({ errorMap: customErrorMap })
    .regex(/^[a-zA-Z0-9]+$/)
    .min(5)
    .max(15),
+  interest: zod.array(zod.string({ errorMap: customErrorMap })).min(1),
  confirm: zod.literal(true),
});

上記ではオプションのerrorMapの値として、今回作成したcustomErrorMapを設定しています。

これで以下の様な出力になります。

  • userId, interestcustomErrorMapのエラーメッセージが出力される
  • confirm:デフォルトのエラーメッセージが出力される

共通のメッセージはグローバルのZodErrorMapから出力し、一部の項目は個別のスキーマにバインドしたZodErrorMapから出力する

これがよくあるパターンかなと思います。
.maxなどは共通で設定されているエラーメッセージから出力し、一部項目は個別のスキーマ単位で設定する方法です。

先程のcustomErrorMapはグローバルに適用するように設定します。

// zodカスタムエラーメッセージをグローバルに適用
zod.setErrorMap(customErrorMap);

このuserSchemaのエラー時だけ利用する、カスタムのuserSchemaErrorMapを作成します。

userSchemaErrorMap.ts
import * as zod from 'zod';
import { customErrorMap } from '@/util/zod/customErrorMap';

/**
 * userSchema用カスタムエラーメッセージ
 */
const userSchemaErrorMap: zod.ZodErrorMap = (issue, ctx) => {
  switch (issue.code) {
    case zod.ZodIssueCode.invalid_literal:
      // true/falseチェックボックス用メッセージ
      return { message: 'チェック必須です' };

    case zod.ZodIssueCode.invalid_string:
      if (issue.validation === 'regex') {
        return { message: '半角英数字で入力してください。' };
      }
  }

  // グローバルErrorMapのメッセージを返す
  return { message: customErrorMap(issue, ctx).message };
};

グローバルのエラーマップとスキーマのエラーマップの違い

グローバルのエラーマップ(customErrorMap)と、
スキーマのエラーマップ(userSchemaErrorMap)の違いは、
いずれのエラーコードにも合致しないエラーが発生した場合のメッセージの指定です。

グローバルのエラーマップでは、zodのデフォルトメッセージを返しています。

customErrorMap(グローバル)の場合
// デフォルトのメッセージを返す
return { message: ctx.defaultError };

一方スキーマのエラーマップでは、グローバルのcustomErrorMapのメッセージを返しています。

userSchemaErrorMap(スキーマ)の場合
// グローバルErrorMapのメッセージを返す
return { message: customErrorMap(issue, ctx).message };

こう指定することで、
スキーマのエラーマップから該当するエラーメッセージを探す
→ グローバルのエラーマップから該当するエラーメッセージを探す
→ デフォルトのエラーメッセージを探す

となります。

これによって
共通のメッセージは、グローバルのZodErrorMapから出力し、
一部の項目は、個別のスキーマにバインドしたZodErrorMapから出力する

が実現できます。

最後に、このスキーマのエラーマップ(userSchemaErrorMap)を適用したい項目に設定します。

userSchema.ts
const userSchema = zod.object({
  userId: zod
+    .string({ errorMap: userSchemaErrorMap })
    .regex(/^[a-zA-Z0-9]+$/)
    .min(5)
    .max(15),
  interest: zod.array(zod.string()).min(1),
+  confirm: zod.literal(true, { errorMap: userSchemaErrorMap }),
});

上記の場合、以下の様な出力になります。

  • .regex, .literalのエラー:userSchemaErrorMapからエラーメッセージを表示する
  • .min, .maxのエラー:グローバルのcustomErrorMapからエラーメッセージを表示する

ZodErrorMapを活用して、良きzodライフを!

23
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?