- 技術 💻
Zodの使い方|v4対応のAPI早見表とエラー出力つき実例3つ
Zodとは、「データの形(スキーマ)」を1回書くだけで、実行時の入力チェックとTypeScriptの型の両方を得られるバリデーションライブラリです。 z.object({...}) でスキーマを作り、safeParse() で検証し、z.infer<typeof スキーマ> で型を取り出す、の3つが基本です。
この記事は Zod v4(2026年9月28日時点のnpm最新 4.6.5) を前提に、よく使うAPIを一覧表にまとめ、v3からの変更点も表で整理しました。すべてのコードを Node.js v22.22.2 で実行し、safeParse の実際のエラー出力をそのまま掲載 しています。さらに実例として、フォーム(React Hook Form 7.89.0 + @hookform/resolvers 5.9.1、TypeScript 7.0.2 で型チェック)、環境変数のチェック、API応答の検証の3つを、コピペで使える形で紹介します。
Zod v4 API早見表
「何を検証したいか」から、使うAPIと失敗したときの既定のメッセージを引けます。 メッセージは Node v22.22.2 + zod 4.6.5 で実際に出力されたものです。
| やりたいこと | 書き方(v4) | 失敗時の既定メッセージ(実測) |
|---|---|---|
| 文字列 | z.string() |
Invalid input: expected string, received number |
| 文字数 | z.string().min(1).max(50) |
Too small: expected string to have >=20 characters |
| 前後の空白を除く | z.string().trim() |
― |
| 数値・整数 | z.number() / z.number().int() |
Invalid input: expected int, received number |
| 範囲 | .min(0) / .positive() / .nonnegative() |
Too small: expected number to be >0 |
| 真偽値 | z.boolean() |
― |
| メール | z.email() |
Invalid email address |
| URL | z.url() |
Invalid URL |
| UUID | z.uuid()(ゆるく判定するなら z.guid()) |
Invalid UUID |
| IPアドレス | z.ipv4() / z.ipv6() |
Invalid IPv4 address |
| 日付文字列 | z.iso.date()(YYYY-MM-DD) |
Invalid ISO date |
| 日時文字列 | z.iso.datetime() |
Invalid ISO datetime |
| 決まった値のどれか | z.enum(['todo', 'done']) |
Invalid option: expected one of "todo"|"done" |
| 固定値 | z.literal('created') |
― |
| オブジェクト(余分なキーは捨てる) | z.object({...}) |
― |
| 余分なキーをエラーに | z.strictObject({...}) |
Unrecognized key: "extra" |
| 余分なキーを残す | z.looseObject({...}) |
― |
| 省略可 | .optional()(undefined を許す) |
― |
null を許す |
.nullable() / 両方なら .nullish() |
― |
| 既定値 | .default(10) |
― |
| 配列 | z.array(z.string()).min(1) |
Too small: expected array to have >=1 items |
| 固定長の配列 | z.tuple([z.number(), z.number()]) |
― |
| キーが自由な辞書 | z.record(z.string(), z.number()) |
― |
| どれかの型 | z.union([z.string(), z.number()]) |
Invalid input |
| 種類で分岐 | z.discriminatedUnion('type', [...]) |
Invalid discriminator value. Expected 'created' | 'deleted' |
| 独自ルール | .refine(fn, { error: '...' }) |
指定したメッセージ |
| 複数のエラーを出す独自ルール | .superRefine((v, ctx) => ctx.addIssue(...)) |
指定したメッセージ |
| 値を変換 | .transform(fn) |
― |
| 変換後にさらに検証 | .pipe(z.number()) |
― |
| 文字列を数値などに変換 | z.coerce.number() / z.coerce.date() |
Invalid input: expected number, received NaN |
"true"/"false" を真偽値に |
z.stringbool() |
― |
| 型を取り出す | z.infer<typeof S>(=z.output)/ z.input<typeof S> |
― |
| 検証(例外を投げる) | S.parse(data) |
ZodError を throw |
| 検証(結果を返す) | S.safeParse(data) → { success, data, error } |
― |
| 非同期の検証 | S.parseAsync() / S.safeParseAsync() |
― |
| エラーを読みやすい文字列に | z.prettifyError(error) |
✖ メッセージ → at パス |
| エラーをネスト構造に | z.treeifyError(error) |
― |
| エラーをフォーム向けの平らな形に | z.flattenError(error) |
― |
| 日本語メッセージにする | z.config(z.locales.ja()) |
無効な入力: 数値が期待されましたが、stringが入力されました |
| JSON Schemaに変換 | z.toJSONSchema(S) |
― |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Zodの使い方|v4対応のAPI早見表とエラー出力つき実例3つ」
インストールと最初の一歩
npm install zod
zod@4.6.5(2026年9月28日時点の npm latest)
Zod v4 は TypeScript 5.5 以降でテストされています。 tsconfig.json では "strict": true を有効にしておくと、推論された型が正しく働きます。
import * as z from 'zod';
const User = z.object({
name: z.string().min(1, { error: '名前は必須です' }),
email: z.email({ error: 'メールアドレスの形式が正しくありません' }),
age: z.number().int().min(0).optional(),
role: z.enum(['admin', 'member']).default('member'),
});
type User = z.infer<typeof User>;
// { name: string; email: string; age?: number | undefined; role: "admin" | "member" }
console.log(User.parse({ name: '佐藤', email: 'sato@example.com' }));
実行結果(Node v22.22.2 / zod 4.6.5):
{ name: '佐藤', email: 'sato@example.com', role: 'member' }
role を渡していないのに 'member' が入っているのは .default() の効果です。parse の戻り値は「検証済みで、変換・既定値も反映されたデータ」なので、以降はこちらを使います。
safeParseとparseの違い、実際のエラー出力
parse は失敗すると例外(ZodError)を投げ、safeParse は { success: false, error } を返します。 画面にエラーを表示するなど「失敗が普通に起こる」場所では safeParse、失敗したら処理を止めてよい場所では parse が向いています。
const r = User.safeParse({ name: '', email: 'sato@', age: -1.5 });
console.log(r.success); // false
console.log(r.error.issues); // 生のエラー一覧
実行結果(issues、email の pattern は長いため省略):
[
{ "origin": "string", "code": "too_small", "minimum": 1, "inclusive": true, "path": ["name"], "message": "名前は必須です" },
{ "origin": "string", "code": "invalid_format", "format": "email", "path": ["email"], "message": "メールアドレスの形式が正しくありません" },
{ "expected": "int", "format": "safeint", "code": "invalid_type", "path": ["age"], "message": "Invalid input: expected int, received number" }
]
エラーは最初の1つで止まらず、すべての項目についてまとめて返ります。 age: -1.5 は「整数でない」で失敗したため、min(0) のエラーは出ていません。
エラーを整形する3つの関数
v4 では、エラーの整形は z.prettifyError / z.treeifyError / z.flattenError の3つの関数で行います。 v3 の error.format() / error.flatten() はまだ動きますが、非推奨になりました。
console.log(z.prettifyError(r.error));
✖ 名前は必須です
→ at name
✖ メールアドレスの形式が正しくありません
→ at email
✖ Invalid input: expected int, received number
→ at age
console.log(z.treeifyError(r.error));
{
"errors": [],
"properties": {
"name": { "errors": ["名前は必須です"] },
"email": { "errors": ["メールアドレスの形式が正しくありません"] },
"age": { "errors": ["Invalid input: expected int, received number"] }
}
}
console.log(z.flattenError(r.error));
{
"formErrors": [],
"fieldErrors": {
"name": ["名前は必須です"],
"email": ["メールアドレスの形式が正しくありません"],
"age": ["Invalid input: expected int, received number"]
}
}
| 関数 | 形 | 向いている場面 |
|---|---|---|
z.prettifyError |
人が読む文字列 | ログ、起動時のエラー表示、CLI |
z.treeifyError |
入れ子のオブジェクト(配列の要素は items) |
ネストしたデータの項目ごとの表示 |
z.flattenError |
1階層のオブジェクト | 1階層のフォームの項目ごとの表示 |
エラーメッセージを日本語にする
項目ごとのメッセージは { error: '...' } で指定し、既定のメッセージをまとめて日本語にするなら z.config(z.locales.ja()) を1回呼びます。
z.config(z.locales.ja());
const r = z.object({ n: z.number(), s: z.string().min(3) }).safeParse({ n: 'x', s: 'a' });
console.log(z.prettifyError(r.error));
✖ 無効な入力: 数値が期待されましたが、stringが入力されました
→ at n
✖ 小さすぎる値: stringは3文字以上である必要があります
→ at s
error には関数も渡せます。「未入力」と「型が違う」でメッセージを分けたいときに便利です。
const Name = z.string({
error: (iss) => (iss.input === undefined ? '必須です' : '文字列で入力してください'),
});
Name.safeParse(undefined); // → "必須です"
よく使うスキーマの書き方と実行結果
オブジェクトの余分なキーの扱い(object / strictObject / looseObject)
入力 { id: 1, name: 'ノート', extra: 'x' } に対して:
| スキーマ | 結果(実測) |
|---|---|
z.object({ id, name }) |
成功:{"id":1,"name":"ノート"}(extra は取り除かれる) |
z.strictObject({ id, name }) |
失敗:Unrecognized key: "extra" |
z.looseObject({ id, name }) |
成功:{"id":1,"name":"ノート","extra":"x"} |
APIに送る前のデータ整形なら z.object(余分なものを捨てる)、設定ファイルのタイプミスを見つけたいなら z.strictObject が向いています。オブジェクトの組み立てには .extend({...})、.pick({ name: true })、.omit({...})、.partial() が使えます(.merge() は非推奨で、.extend() を使います)。
optional・nullable・default の違い
const O = z.object({
a: z.string().optional(), // undefined を許す
b: z.string().nullable(), // null を許す(undefined は不可)
c: z.string().nullish(), // どちらも許す
d: z.number().default(10), // undefined なら 10
});
| 入力 | 結果(実測) |
|---|---|
{ b: null } |
成功:{"b":null,"d":10} |
{} |
失敗:Invalid input: expected string, received undefined → at b |
nullable() は「キーがない」ことまでは許しません。 APIが項目を省略することがあるなら nullish() を使います。
v4 では .default() の値は「出力の型」で、変換(transform)を通らずにそのまま返ります。 入力として扱い、変換も通したいときは .prefault() を使います。
const Len = z.string().transform((s) => s.length);
Len.default(0).parse(undefined); // → 0(そのまま)
Len.prefault('abc').parse(undefined); // → 3('abc' を変換した結果)
union と discriminatedUnion
「種類を表すキー」で形が変わるデータは z.discriminatedUnion にすると、エラーが具体的になります。
const Event = z.discriminatedUnion('type', [
z.object({ type: z.literal('created'), id: z.number() }),
z.object({ type: z.literal('deleted'), id: z.number(), reason: z.string() }),
]);
| 入力 | 結果(実測) |
|---|---|
{ type: 'deleted', id: 3, reason: '重複' } |
成功 |
{ type: 'moved', id: 3 } |
Invalid discriminator value. Expected 'created' | 'deleted' → at type |
{ type: 'deleted', id: 3 } |
Invalid input: expected string, received undefined → at reason |
(参考)z.union([z.string(), z.number()]) に true |
Invalid input |
z.union はどの候補にも合わないと Invalid input だけになりますが、discriminatedUnion は type を見て候補を1つに絞るので、「どの項目が足りないか」まで分かります。
配列・タプル・レコード
| スキーマ | 入力 | 結果(実測) |
|---|---|---|
z.array(z.string()).min(1) |
[] |
Too small: expected array to have >=1 items |
z.tuple([z.number(), z.number()]) |
[1, '2'] |
Invalid input: expected number, received string → at [1] |
z.record(z.string(), z.number()) |
{ pen: 3, note: 'x' } |
Invalid input: expected number, received string → at note |
z.record(z.enum(['a','b']), z.number()) |
{ a: 1 } |
Invalid input: expected number, received undefined → at b |
z.partialRecord(z.enum(['a','b']), z.number()) |
{ a: 1 } |
成功 |
v4 では z.record のキーに z.enum を渡すと「すべてのキーが必須」になります。 一部だけでよいなら z.partialRecord を使います。
独自ルール(refine / superRefine)
refine は「真偽を返す関数」で1つのルールを足し、path でエラーを付ける項目を指定できます。
const Password = z
.object({ pw: z.string().min(8), confirm: z.string() })
.refine((v) => v.pw === v.confirm, {
error: 'パスワードが一致しません',
path: ['confirm'],
});
Password.safeParse({ pw: 'abcdefgh', confirm: 'abcdefgX' });
✖ パスワードが一致しません
→ at confirm
1回の検証で複数のエラーを出したいときは superRefine で ctx.addIssue() を呼びます。
const Tags = z.array(z.string()).superRefine((arr, ctx) => {
const seen = new Set();
arr.forEach((t, i) => {
if (seen.has(t)) ctx.addIssue({ code: 'custom', message: `「${t}」が重複しています`, path: [i] });
seen.add(t);
});
});
Tags.safeParse(['a', 'b', 'a', 'b']);
✖ 「a」が重複しています
→ at [2]
✖ 「b」が重複しています
→ at [3]
変換(transform / pipe)
transform で値を変換し、pipe で変換後の値をさらに検証できます。
z.string().transform((s) => s.trim().toUpperCase()).parse(' abc '); // → "ABC"
const PositiveFromString = z
.string()
.transform((s) => Number(s))
.pipe(z.number().int().positive());
PositiveFromString.safeParse('-3');
// ✖ Too small: expected number to be >0
coerce の落とし穴(実測)
z.coerce.* は、検証の前に JavaScript の Number() / Boolean() / new Date() で変換します。 そのため、直感と違う結果になる入力があります。
| スキーマ | 入力 | 結果(実測) | 理由 |
|---|---|---|---|
z.coerce.number() |
'42' |
成功:42 |
Number('42') |
z.coerce.number() |
'abc' |
失敗:expected number, received NaN |
Number('abc') は NaN |
z.coerce.number() |
''(空文字) |
成功:0 |
Number('') は 0 |
z.coerce.boolean() |
'false' |
成功:true |
空でない文字列は true |
z.stringbool() |
'false' |
成功:false |
"true"/"false"/"1"/"0"/"yes"/"no" などを解釈 |
z.coerce.date() |
'2026-09-28' |
成功:2026-09-28T00:00:00.000Z |
new Date() |
z.iso.date() |
'2026-02-30' |
失敗:Invalid ISO date |
存在しない日付 |
フォームの空欄を「未入力」として扱いたいなら、coerce.number() の前に空文字を弾く(z.string().min(1) → pipe)か、.min() などの範囲チェックを必ず付けます。 環境変数の "false" は z.coerce.boolean() ではなく z.stringbool() で読みます。
型の取り出し(z.infer / z.input / z.output)
z.infer は検証「後」の型(z.output と同じ)です。 coerce や default、transform を使うと、入力と出力の型が変わります。
const Item = z.object({
id: z.number(),
name: z.string(),
qty: z.coerce.number().int().default(1),
tags: z.array(z.string()).optional(),
});
type Item = z.infer<typeof Item>; // 検証後:qty は number
type ItemInput = z.input<typeof Item>; // 検証前:qty は unknown(何でも受け付ける)
const a: Item = { id: 1, name: 'ペン', qty: 2 };
// @ts-expect-error qty は number
const b: Item = { id: 1, name: 'ペン', qty: '2' };
const c: ItemInput = { id: 1, name: 'ペン', qty: '2' };
TypeScript 7.0.2(strict: true)で型チェックし、b の行だけが意図どおり型エラーになることを確認しました。スキーマと型を同じ名前(Item)で定義しておくと、Item.parse() と : Item を同じ名前で使えて読みやすくなります。
v3からv4の主な変更点
公式の移行ガイド(zod.dev/v4/changelog)をもとに、よく使う部分をまとめました。「動作(4.6.5)」は実際に試した結果です。
| 項目 | v3 | v4 | 動作(4.6.5) |
|---|---|---|---|
| 文字列フォーマット | z.string().email() |
z.email()(z.url()、z.uuid() なども同様) |
旧書き方も動く(非推奨) |
| エラーメッセージの指定 | { message: '...' } |
{ error: '...' }(関数も可) |
message も動く(非推奨) |
| 型エラー・必須エラーの指定 | invalid_type_error / required_error |
error: (iss) => ... で分岐 |
旧オプションは削除 |
| 余分なキーをエラーに | .strict() |
z.strictObject() |
.strict() も動く(非推奨) |
| 余分なキーを残す | .passthrough() |
z.looseObject() |
.passthrough() も動く(非推奨) |
| オブジェクトの結合 | .merge() |
.extend() |
.merge() も動く(非推奨) |
| 深い partial | .deepPartial() |
削除 | undefined(存在しない) |
| レコード | z.record(値) 可 |
z.record(キー, 値) 必須 |
1引数は型エラー TS2554: Expected 2-3 arguments, but got 1. |
| エラー整形 | error.format() / error.flatten() |
z.treeifyError() / z.flattenError() / z.prettifyError() |
旧メソッドも動く(非推奨) |
| enum | z.nativeEnum(Enum) |
z.enum(Enum) |
nativeEnum も動く(非推奨) |
| default | 入力の型の値 | 出力の型の値。入力として扱うなら .prefault() |
上の実測参照 |
| UUID | ゆるい判定 | RFC準拠で厳密(ゆるくは z.guid()) |
― |
| IP | z.string().ip() |
z.ipv4() / z.ipv6() |
― |
v3 から移る場合は、まず非推奨の書き方のまま v4 に上げて動作を確認し、その後 z.email()・error・z.strictObject() へ順に書き換えると、差分を小さく保てます。
実例1:フォームの検証(React Hook Form)
React Hook Form の zodResolver にスキーマを渡すと、送信時に Zod で検証され、errors にメッセージが入ります。 coerce を使う項目があるため、useForm に「入力の型」と「出力の型」を分けて渡しています。
npm install zod react-hook-form @hookform/resolvers
import * as z from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const SignupSchema = z
.object({
name: z.string().trim().min(1, { error: '名前を入力してください' }),
email: z.email({ error: 'メールアドレスの形式が正しくありません' }),
age: z.coerce.number<string>({ error: '数字で入力してください' }).int().min(18, { error: '18歳以上が対象です' }),
password: z.string().min(8, { error: '8文字以上で入力してください' }),
confirm: z.string(),
})
.refine((v) => v.password === v.confirm, {
error: 'パスワードが一致しません',
path: ['confirm'],
});
type SignupInput = z.input<typeof SignupSchema>;
type SignupOutput = z.output<typeof SignupSchema>;
export function SignupForm({ onSubmit }: { onSubmit: (data: SignupOutput) => void }) {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<SignupInput, unknown, SignupOutput>({
resolver: zodResolver(SignupSchema),
});
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register('name')} placeholder="名前" />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} placeholder="メール" />
{errors.email && <p>{errors.email.message}</p>}
<input {...register('age')} inputMode="numeric" placeholder="年齢" />
{errors.age && <p>{errors.age.message}</p>}
<input type="password" {...register('password')} />
{errors.password && <p>{errors.password.message}</p>}
<input type="password" {...register('confirm')} />
{errors.confirm && <p>{errors.confirm.message}</p>}
<button disabled={isSubmitting}>登録</button>
</form>
);
}
このコンポーネントは TypeScript 7.0.2 + react-hook-form 7.89.0 + @hookform/resolvers 5.9.1 で型チェックが通ることを確認しました(ブラウザでの画面操作は未実行)。 z.coerce.number<string>() と書くと入力の型が string になり、<input> の値(常に文字列)と型が合います。
同じスキーマを Node で safeParse した結果です(resolver の中で行われる検証と同じ処理)。
入力:{ name: ' ', email: 'user@', age: 'abc', password: 'pass', confirm: 'pass2' }
✖ 名前を入力してください
→ at name
✖ メールアドレスの形式が正しくありません
→ at email
✖ 数字で入力してください
→ at age
✖ 8文字以上で入力してください
→ at password
入力:{ name: '田中', email: 'tanaka@example.com', age: '20', password: 'password1', confirm: 'password2' }
✖ パスワードが一致しません
→ at confirm
1つ目で「パスワードが一致しません」が出ていないのは、オブジェクト全体の refine は各項目の検証がすべて通ってから実行されるためです。項目のエラーを直すと、2つ目のように一致チェックのエラーが表示されます。成功時の name は trim() により ' 田中 ' → '田中'、age は '20' → 20(数値)に変換されて onSubmit に届きます。
実例2:環境変数のチェック
アプリの起動時に process.env を検証しておくと、設定漏れを「最初の1行」で見つけられます。 環境変数はすべて文字列なので、数値は coerce、真偽値は stringbool で変換します。
// env.ts
import * as z from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
DATABASE_URL: z.url({ protocol: /^postgres(ql)?$/ }),
API_TOKEN: z.string().min(20),
ENABLE_CACHE: z.stringbool().default(false),
});
const result = EnvSchema.safeParse(process.env);
if (!result.success) {
console.error('環境変数の設定に誤りがあります:\n' + z.prettifyError(result.error));
process.exit(1);
}
export const env = result.data; // 型つき:env.PORT は number
設定に誤りがある場合(PORT=abc DATABASE_URL=mysql://localhost/app API_TOKEN=short)の実行結果:
環境変数の設定に誤りがあります:
✖ Invalid input: expected number, received NaN
→ at PORT
✖ Invalid URL
→ at DATABASE_URL
✖ Too small: expected string to have >=20 characters
→ at API_TOKEN
終了コードは 1 でした。正しく設定した場合(PORT=8080、ENABLE_CACHE=false など)は、env.PORT が数値の 8080、env.ENABLE_CACHE が真偽値の false、未設定の NODE_ENV は 'development' になりました。DATABASE_URL の protocol オプションで、URLの形式だけでなく「postgres で始まること」まで確かめています。
実例3:API応答の検証
外部APIの応答は TypeScript の型だけでは保証されないため、受け取った直後に Zod で検証すると、形が違うデータを早い段階で見つけられます。 手元のテストサーバー(localhost:7152)で、正しい応答と形の違う応答を返して試しました。
import * as z from 'zod';
const OrderSchema = z.object({
id: z.number().int(),
status: z.enum(['pending', 'shipped', 'delivered']),
items: z
.array(
z.object({
sku: z.string(),
price: z.number().nonnegative(),
qty: z.number().int().positive(),
}),
)
.min(1),
createdAt: z.iso.datetime().transform((s) => new Date(s)),
note: z.string().nullish(),
});
type Order = z.infer<typeof OrderSchema>;
async function fetchOrder(id: number): Promise<Order> {
const res = await fetch(`http://localhost:7152/orders/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const result = OrderSchema.safeParse(await res.json());
if (!result.success) {
throw new Error(`APIの応答が想定と違います\n${z.prettifyError(result.error)}`);
}
return result.data;
}
正しい応答(createdAt: '2026-09-28T09:00:00Z'、price: 120, qty: 2)の結果:
createdAt instanceof Date → true
createdAt.toISOString() → 2026-09-28T09:00:00.000Z
price × qty → 240
形の違う応答(status: 'lost'、price: '300'、createdAt: '2026/09/28')の結果:
APIの応答が想定と違います
✖ Invalid option: expected one of "pending"|"shipped"|"delivered"
→ at status
✖ Invalid ISO datetime
→ at createdAt
✖ Invalid input: expected number, received string
→ at items[0].price
transform で日時文字列を Date に変換しているので、呼び出し側は検証済み・変換済みのデータだけを扱えます。 エラーの → at items[0].price のように、配列の中の場所まで示されるのでログから原因を追いやすくなります。
よくある質問
Q. parse と safeParse はどちらを使えばいいですか?
A. 入力エラーを画面に表示するなど失敗が想定される場所では safeParse、失敗したら処理を中断してよい場所では parse が向いています。parse は失敗すると ZodError を投げ、safeParse は { success: false, error } を返します。
Q. z.string().email() はv4で使えなくなりましたか?
A. zod 4.6.5 でも動きますが、非推奨です。 v4 では z.email() のようにトップレベルの関数として書くのが推奨で、z.url()、z.uuid()、z.ipv4() なども同じ形になりました。
Q. エラーメッセージをまとめて日本語にできますか?
A. z.config(z.locales.ja()) を1回呼ぶと、既定のメッセージが日本語になります。 項目ごとに文言を変えたい場合は { error: '名前を入力してください' } のように個別に指定します。
Q. z.infer と z.input の違いは何ですか?
A. z.infer(=z.output)は検証後の型、z.input は検証前の型です。coerce・default・transform を使うと両者が変わるので、フォームの入力値には z.input、送信処理に渡すデータには z.output を使います。
まとめ
- Zod はスキーマを1回書くだけで、実行時の検証と TypeScript の型を両方得られる。基本は
z.object→safeParse→z.infer。 - v4 では
z.email()などのフォーマットがトップレベルになり、メッセージは{ error }、オブジェクトはz.strictObject/z.looseObjectで書く。 - エラー整形は
z.prettifyError(ログ向け)・z.flattenError(フォーム向け)・z.treeifyError(入れ子向け) の3つ。 z.coerce.number()は空文字を0に、z.coerce.boolean()は'false'をtrueにする。真偽値の文字列はz.stringbool()。- 種類で形が変わるデータは
discriminatedUnionにすると、エラーが具体的になる。 - フォーム・環境変数・API応答の「入口」で検証すると、以降のコードは型どおりのデータだけを扱える。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Zodの使い方|v4対応のAPI早見表とエラー出力つき実例3つ」
株式会社RJCでは、型と実行時の検証を組み合わせた堅実な設計を、プロジェクトの中で仲間と試しながら取り入れています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。