6. エラー整形と49言語i18nロケール
Zod v4では、エラーのカスタマイズと整形APIが根本から再設計されました。v3で分散していたオプション(message、invalid_type_error、required_error、errorMap)は単一の { error } パラメータに統合され、明確な4段階の優先順位チェーン、Tree-Shaking対応のトップレベル整形関数(z.prettifyError、z.treeifyError、z.flattenError)、そして 49種類以上の組み込みi18nロケール によって支えられています。
Source Code
from drawlib.canvas import save, setup
from drawlib.icons import phosphor
from drawlib.lines import line
from drawlib.shapes import rectangle
from drawlib.smartarts import Panel
from drawlib.styles import Colors, Styles
from drawlib.text import text
setup(width=160, height=76)
text(
(80, 71.5),
"Zod v4 エラー解決の優先順位チェーンと整形パイプライン",
style=Styles.PrimaryBold.patch(text_size=13.5),
)
# Left panel: 4-Level Precedence Chain (Highest -> Lowest)
Panel(
"1. エラーマップの4段階優先順位(最初に返された文字列を採用)",
style=Styles.Neutral.patch(shape_fill_color=Colors.White),
header_style=Styles.PrimaryNeutral,
text_style=Styles.PrimaryBold.patch(text_size=10.5),
header_align="center",
).draw((6, 6), width=74, height=60)
levels = [
("優先度1: パース時 { error }", "schema.safeParse(data, { error })", Styles.PrimaryFlat, Styles.WhiteBold, Styles.White),
("優先度2: スキーマ定義 { error }", "z.string({ error: (iss) => ... })", Styles.PrimaryNeutral, Styles.PrimaryBold, Styles.Neutral),
("優先度3: グローバル customError", "z.config({ customError: (iss) => ... })", Styles.Neutral, Styles.PrimaryBold, Styles.Neutral),
("優先度4: ロケールマップ (49+言語)", "z.config(z.locales.ja())", Styles.SecondaryNeutral, Styles.SecondaryBold, Styles.Neutral),
]
for idx, (title_str, code_str, box_style, t_style, sub_style) in enumerate(levels):
cy = 50.0 - idx * 12.2
rectangle((43, cy), width=64, height=9.0, style=box_style.patch(shape_r=1.5))
text((43, cy + 1.8), title_str, style=t_style.patch(text_size=10.5))
text((43, cy - 2.0), code_str, style=sub_style.patch(text_size=10.0))
if idx < 3:
line((43, cy - 4.5), (43, cy - 7.7), arrow_head="->", style=Styles.NeutralBold)
# Center node: $ZodError instance
rectangle((97, 36), width=20, height=18, style=Styles.AccentFlat.patch(shape_r=2.0))
phosphor.warning_octagon((97, 40.5), width=5.5, style=Styles.White)
text((97, 34.0), "ZodError", style=Styles.WhiteBold.patch(text_size=11.5))
text((97, 30.0), ".issues[]", style=Styles.White.patch(text_size=10.0))
line((80, 36), (87, 36), arrow_head="->", style=Styles.PrimaryBold)
# Right panel: Output Formatters
Panel(
"2. Tree-Shake対応フォーマッタ",
style=Styles.Neutral.patch(shape_fill_color=Colors.White),
header_style=Styles.PrimaryNeutral,
text_style=Styles.PrimaryBold.patch(text_size=10.5),
header_align="center",
).draw((114, 6), width=40, height=60)
formatters = [
("z.prettifyError(err)", "CLI / ログ向け整形文字列", Styles.PrimaryNeutral),
("z.treeifyError(err)", "ネスト構造エラーツリー", Styles.PrimaryFlat),
("z.flattenError(err)", "1階層フィールドマップ", Styles.Neutral),
("z.formatError(err)", "旧互換 _errors ツリー", Styles.Neutral),
]
for idx, (fn_name, desc_str, f_style) in enumerate(formatters):
cy = 50.0 - idx * 12.2
is_flat = f_style == Styles.PrimaryFlat
rectangle((134, cy), width=33, height=9.0, style=f_style.patch(shape_r=1.5))
text(
(134, cy + 1.8),
fn_name,
style=(Styles.WhiteBold if is_flat else Styles.PrimaryBold).patch(text_size=10.5),
)
text(
(134, cy - 2.0),
desc_str,
style=(Styles.White if is_flat else Styles.Neutral).patch(text_size=10.0),
)
line((107, 36), (114, 36), arrow_head="->", style=Styles.PrimaryBold)
save()
1. 統一された { error } パラメータによるカスタマイズ
Zod v3では、エラーメッセージの指定方法が複数のAPI(message、invalid_type_error、required_error、errorMap)に分裂していました。Zod v4ではこれらをすべて廃止し、すべてのスキーマコンストラクタ・チェックメソッド・.refine() で共通の error オプションに統一しています。
error プロパティには以下のいずれかを指定できます:
- 固定文字列:
{ error: "メールアドレスの形式が正しくありません" } - エラーマップ関数
(iss: $ZodRawIssue) => string | undefined:undefinedを返すと、優先順位チェーンの次のエラーマップへ処理を委譲します。
import * as z from "zod";
// 1. スキーマ定義およびチェックメソッドでの固定文字列エラー
const Username = z
.string({ error: "ユーザー名は文字列で入力してください" })
.min(3, { error: "ユーザー名は3文字以上で入力してください" });
// 2. 生のIssueオブジェクトを検査する動的エラーマップ関数
// Zod v3の `required_error` と `invalid_type_error` の両方を1つで代替します!
const AgeSchema = z.number({
error: (iss) => {
if (iss.input === undefined) {
return "年齢は必須項目です";
}
if (iss.code === "invalid_type") {
return `数値を期待しましたが、${typeof iss.input} が渡されました`;
}
return undefined; // グローバル設定またはロケールマップへ委譲
},
}).min(18, {
error: (iss) => `${iss.minimum}歳以上である必要があります(入力値: ${iss.input})`,
});
Zod v4で整理・統合された $ZodIssue コード一覧
Zod v4では、issue.code による判別共用体(Discriminated Union)が整理され、より一貫性の高い構造になりました:
Issueコード (issue.code) |
主なメタデータプロパティ | 発生元となるスキーマ・チェック |
|---|---|---|
"invalid_type" |
expected, input |
基本型チェック(z.string(), z.number(), z.object() など) |
"too_small" |
origin, minimum, inclusive, exact |
.min(), .gt(), .gte(), .nonempty(), .minLength() |
"too_big" |
origin, maximum, inclusive, exact |
.max(), .lt(), .lte(), .maxLength() |
"invalid_format" |
format, pattern, prefix, suffix, includes |
z.email(), z.uuid(), .regex(), .startsWith(), z.iso.* |
"invalid_value" |
values, input |
z.literal(), z.enum(), z.nativeEnum()(v3のenum/literal系エラーを統合) |
"not_multiple_of" |
divisor, input |
.multipleOf(), .step() |
"unrecognized_keys" |
keys, input |
z.strictObject() で未定義キーが含まれていた場合 |
"invalid_union" |
errors, discriminator, matches |
z.union(), z.discriminatedUnion(), z.xor() |
"invalid_key" / "invalid_element" |
origin, issues, key |
z.record(), z.map(), z.set() のキーまたは要素の検証失敗 |
"custom" |
params, input |
.refine(), .superRefine(), .check() |
2. 4段階のエラー優先順位チェーン
バリデーションが失敗すると、Zodは以下の厳密な優先順位に従ってエラーマップを評価し、最初に非nullの string(または { message: string })を返したものを issue.message として採用します:
- パース時
{ error }—.parse(data, { error })または.safeParse(data, { error })に直接渡されたエラーマップ。Zod v4では パース時の指定が最優先 されるため、リクエスト単位での言語切り替えや一時的なオーバーライドが容易です。 - スキーマ・チェック単位の
{ error }— エラーを発生させたスキーマまたはチェック自身に定義されたエラーマップ。 - グローバル
customError—z.config({ customError: (iss) => ... })で設定されたアプリケーション共通のエラーマップ。 - ロケール
localeError—z.config(z.locales.ja())で設定された言語別エラーマップ(未設定時はデフォルトの英語ロケールにフォールバック)。
import * as z from "zod";
import { ja } from "zod/locales";
// レベル4: ベースラインのロケールとして日本語を設定
z.config(ja());
// レベル3: 特定のエラーコードに対するグローバルカスタムエラーを設定
z.config({
customError: (iss) => {
if (iss.code === "unrecognized_keys") {
return `未定義のフィールドが含まれています: ${iss.keys.join(", ")}`;
}
return undefined; // レベル4(日本語ロケール)へ委譲
},
});
// レベル2: スキーマ単位のカスタムエラー
const CodeSchema = z.string().min(5, { error: "コードが短すぎます(最低5文字)" });
// レベル2が採用される -> "コードが短すぎます(最低5文字)"
CodeSchema.safeParse("abc");
// レベル1: パース時オプションが最優先で適用される
CodeSchema.safeParse("abc", {
error: () => "リクエスト単位のオーバーライドメッセージ",
});
3. Tree-Shaking対応のエラー整形ユーティリティ
Zod v3では .format() や .flatten() が ZodError.prototype のメソッドとして実装されていたため、err.issues しか参照しないアプリケーションでもバンドラーがそれらのコードを除去(Tree-Shake)できませんでした。Zod v4ではすべての整形処理が トップレベル関数 に移行され、zod・zod/mini・zod/v4/core のすべてで共通して利用できます。
z.prettifyError(err) — CLI・ログ向けの可読性に優れた文字列出力
z.prettifyError(err) は、ZodError(またはStandard Schemaの FailureResult)をパスの深さ順にソートし、toDotPath() によるドット&ブラケット記法を用いた複数行の人間向けサマリー文字列に変換します:
import * as z from "zod";
const TeamSchema = z.object({
teamName: z.string().min(3),
members: z.array(
z.object({
email: z.email(),
role: z.enum(["admin", "member"]),
})
),
});
const result = TeamSchema.safeParse({
teamName: "A",
members: [{ email: "not-an-email", role: "guest" }],
});
if (!result.success) {
console.log(z.prettifyError(result.error));
/* 出力:
✖ Too small: expected string to have >=3 characters
→ at teamName
✖ Invalid email address
→ at members[0].email
✖ Invalid option: expected one of "admin"|"member"
→ at members[0].role
*/
}
z.treeifyError(err) — 型安全なネスト構造エラーツリー
z.treeifyError(err, mapper?) は、Zod v3の .format() が抱えていた「予約キー _errors がオブジェクトのプロパティ名と衝突する」という設計上の問題を解決した新しい標準フォーマッタです。ツリーの各ノードは { errors: U[], properties?: { ... }, items?: [ ... ] } という明確な構造を持ちます:
- オブジェクトのプロパティ(
stringパス要素)は.properties.<key>配下に格納されます。 - 配列やタプルの要素(
numberパス要素)は.items[index]配下に格納されます。 - 第2引数
mapper: (issue: $ZodIssue) => Uを渡すことで、単なる文字列だけでなく{ code: issue.code, message: issue.message }のようなカスタムオブジェクトをツリー内に格納することも可能です。
if (!result.success) {
const tree = z.treeifyError(result.error);
// ルート階層(path: [])のフォーム全体エラー
console.log(tree.errors); // []
// オブジェクトプロパティのエラー
console.log(tree.properties?.teamName?.errors);
// ["Too small: expected string to have >=3 characters"]
// ネストされた配列要素内のプロパティエラー
console.log(tree.properties?.members?.items?.[0]?.properties?.email?.errors);
// ["Invalid email address"]
}
z.flattenError(err) — 1階層フォーム向けのフラット化
ネストを持たないシンプルなHTMLフォームや浅いAPIリクエストを検証する場合、z.flattenError(err, mapper?) を使うと最上位キー(path[0])ごとにエラーメッセージを配列として集約できます:
if (!result.success) {
const flat = z.flattenError(result.error);
// flat.formErrors: string[] (path: [] のエラー)
// flat.fieldErrors: { teamName?: string[]; members?: string[] }
console.log(flat.fieldErrors.teamName);
// ["Too small: expected string to have >=3 characters"]
}
4. 49言語以上の組み込みi18nロケール (zod/locales)
Zod v4には 49種類以上の言語・地域ロケール が公式パッケージ内に標準搭載されており、外部の zod-i18n-map 等を導入する必要がありません。各ロケールモジュールは個別にTree-Shake可能なため、インポートした言語だけがバンドルに含まれます。
サポートされているロケール一覧
| 地域 | 組み込みロケールモジュール (zod/locales/<code.js>) |
|---|---|
| 東アジア・東南アジア | ja (日本語), zhCN (zh-CN), zhTW (zh-TW), ko (韓国語), vi (ベトナム語), th (タイ語), id (インドネシア語), ms (マレー語), km / kh (クメール語) |
| 南アジア・中央アジア | hi (ヒンディー語), bn (ベンガル語), ta (タミル語), gu (グジャラート語), kn (カンナダ語), ne (ネパール語), ur (ウルドゥー語), ps (パシュトー語), tg (タジク語), tk (トルクメン語), uz (ウズベク語) |
| 西欧・北欧 | en (英語), de (ドイツ語), fr (フランス語), frCA (fr-CA), es (スペイン語), it (イタリア語), pt (ポルトガル語), ptBR (pt-BR), nl (オランダ語), sv (スウェーデン語), da (デンマーク語), fi (フィンランド語), no / nn (ノルウェー語), is (アイスランド語), ca (カタロニア語), eo (エスペラント語) |
| 中欧・東欧 | pl (ポーランド語), cs (チェコ語), sk (スロバキア語), hu (ハンガリー語), ro (ルーマニア語), bg (ブルガリア語), hr (クロアチア語), sl (スロベニア語), lt (リトアニア語), el (ギリシャ語), ru (ロシア語), uk / ua (ウクライナ語), be (ベラルーシ語), mk (マケドニア語) |
| 中東・コーカサス・アフリカ | ar (アラビア語), he (ヘブライ語), fa (ペルシア語), tr (トルコ語), ota (オスマン語), az (アゼルバイジャン語), hy (アルメニア語), ka (ジョージア語), ckb (中央クルド語), yo (ヨルバ語) |
グローバル設定とリクエスト単位のロケール切り替え
アプリケーション起動時に z.config(ja()) でグローバルロケールを設定する方法に加え、マルチテナントのAPIサーバーでは { error } パースオプション(優先度1)を使ってリクエストの Accept-Language ごとにロケールを切り替えることができます:
import * as z from "zod";
import { de, en, ja } from "zod/locales";
// 方法A: アプリケーション起動時にデフォルトロケールを日本語に設定
z.config(ja());
const UserSchema = z.object({
name: z.string().min(2),
email: z.email(),
});
const resJa = UserSchema.safeParse({ name: "A", email: "invalid" });
if (!resJa.success) {
console.log(z.prettifyError(resJa.error));
/*
✖ 小さすぎる値: stringは2文字以上である必要があります
→ at name
✖ 無効なメールアドレス
→ at email
*/
}
// 方法B: HTTPハンドラ内でリクエスト単位のロケールを適用(優先度1)
const locales = { en: en(), ja: ja(), de: de() } as const;
function validateWithLocale(payload: unknown, lang: "en" | "ja" | "de") {
return UserSchema.safeParse(payload, {
error: locales[lang].localeError,
});
}
次のステップ
- 7. メタデータレジストリとJSON Schema双方向変換 へ進み、
z.registry()による型安全なメタデータ管理とz.toJSONSchema()/z.fromJSONSchema()による双方向変換を学びましょう。 - 8. zod/miniによるTree-ShakingとJITコンパイル へ進み、フロントエンドのバンドルサイズ削減とバックエンドの高速化手法を確認しましょう。