Zod 全体像を図解で理解:プリミティブ → 合成 → 効果(transform/coerce/refine)の流れ

TypeScript でのデータ検証において、Zod は開発者にとって非常に強力なライブラリです。しかし、その豊富な機能の全体像を把握するのは初心者にとって難しいことがあります。
本記事では、Zod の機能を「プリミティブ型」→「合成型」→「効果」という 3 つの段階に分けて、図解とともにわかりやすく解説いたします。この流れを理解することで、Zod の真の力を活用できるようになるでしょう。
Zod とは:TypeScript 型検証の革新
Zod は、TypeScript のためのスキーマ検証ライブラリです。従来のバリデーションライブラリとは異なり、TypeScript の型システムと密接に連携し、型安全性とランタイム検証を両立させます。
Zod の特徴
Zod が他のライブラリと異なる点は、以下の 3 つの特徴にあります。
typescriptimport { z } from 'zod';
// TypeScriptの型を自動推論
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
// 型も同時に生成される
type User = z.infer<typeof UserSchema>;
図で理解する Zod の基本構造
mermaidflowchart TD
primitive[プリミティブ型] --> compose[合成型]
compose --> effect[効果]
primitive --> |基本的な型定義| string[string, number, boolean]
compose --> |複雑な構造| object[object, array, union]
effect --> |高度な処理| transform[transform, coerce, refine]
この図が示すように、Zod は段階的に機能が積み重なる設計となっています。基礎から応用まで、一つずつ理解していくことが重要です。
段階 | 機能 | 役割 |
---|---|---|
1 | プリミティブ型 | 基本的なデータ型の検証 |
2 | 合成型 | 複雑なデータ構造の構築 |
3 | 効果 | データ変換と高度な検証 |
プリミティブ型:Zod の基礎となる型定義
プリミティブ型は、Zod の最も基本的な構成要素です。文字列、数値、真偽値といった基本的なデータ型から始まります。
文字列・数値・真偽値の基本型
最初に、最もシンプルなプリミティブ型から見ていきましょう。
typescriptimport { z } from 'zod';
// 基本的なプリミティブ型の定義
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
これらの基本型には、さらに詳細な制約を追加できます。
typescript// 文字列の制約
const emailSchema = z.string().email();
const minLengthSchema = z.string().min(3);
const maxLengthSchema = z.string().max(100);
// 数値の制約
const positiveSchema = z.number().positive();
const integerSchema = z.number().int();
const rangeSchema = z.number().min(0).max(100);
実際の検証処理では、以下のように使用します。
typescript// 検証の実行
const result = emailSchema.safeParse('user@example.com');
if (result.success) {
console.log('有効なメール:', result.data);
} else {
console.log('エラー:', result.error.issues);
}
配列・オブジェクトの基本構造
プリミティブ型を組み合わせて、配列やオブジェクトの構造を定義できます。
typescript// 配列の定義
const stringArraySchema = z.array(z.string());
const numberArraySchema = z.array(z.number());
// 基本的なオブジェクトの定義
const personSchema = z.object({
name: z.string(),
age: z.number(),
isActive: z.boolean(),
});
より複雑な構造も段階的に構築できます。
typescript// ネストしたオブジェクト
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string(),
});
const userWithAddressSchema = z.object({
name: z.string(),
email: z.string().email(),
address: addressSchema,
});
プリミティブ型の関係図
mermaidflowchart LR
basic["基本型"] --> advanced["拡張型"];
basic --> stringType["string"];
basic --> numberType["number"];
basic --> booleanType["boolean"];
advanced --> arrayType["array"];
advanced --> objectType["object"];
stringType --|制約|--> email[".email()"];
numberType --|制約|--> positive[".positive()"];
arrayType --|要素型|--> stringArray["string[]"];
objectType --|プロパティ|--> nested["ネストした構造"];
プリミティブ型の段階では、個々のデータ型とその基本的な制約を理解することが重要です。この土台があることで、次の合成型での複雑な構造構築が可能になります。
合成型:複雑なデータ構造の構築
合成型では、プリミティブ型を組み合わせて、より複雑で実用的なデータ構造を作成します。これにより、実際のアプリケーションで使用される複雑なデータモデルを表現できるようになります。
オブジェクトスキーマの組み合わせ
複数のオブジェクトスキーマを組み合わせることで、保守性の高いスキーマ設計が可能です。
typescript// 基本的なスキーマ要素
const contactInfoSchema = z.object({
email: z.string().email(),
phone: z.string().optional(),
});
const personalInfoSchema = z.object({
firstName: z.string(),
lastName: z.string(),
birthDate: z.string().datetime(),
});
これらの基本要素を組み合わせて、より大きなスキーマを構築します。
typescript// スキーマの合成
const userProfileSchema = z.object({
id: z.string().uuid(),
personal: personalInfoSchema,
contact: contactInfoSchema,
preferences: z.object({
language: z.enum(['ja', 'en', 'zh']),
timezone: z.string(),
notifications: z.boolean(),
}),
});
実際の使用例では、部分的な更新も可能です。
typescript// 部分的な更新用スキーマ
const updateUserSchema = userProfileSchema.partial();
// 特定フィールドのみ必須
const essentialUserSchema = userProfileSchema.pick({
id: true,
personal: true,
});
Union・Intersection 型の活用
Union 型と Intersection 型を使用することで、柔軟なデータ構造を表現できます。
typescript// Union型:複数の型のいずれか
const paymentMethodSchema = z.union([
z.object({
type: z.literal('credit_card'),
cardNumber: z.string(),
expiryDate: z.string(),
}),
z.object({
type: z.literal('bank_transfer'),
accountNumber: z.string(),
routingNumber: z.string(),
}),
]);
Intersection 型では、複数の型を組み合わせます。
typescript// Intersection型:複数の型を結合
const timestampSchema = z.object({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
const auditableUserSchema = z.intersection(
userProfileSchema,
timestampSchema
);
より実践的な例として、API レスポンスの型定義を見てみましょう。
typescript// APIレスポンスの合成型
const apiResponseSchema = z.union([
z.object({
success: z.literal(true),
data: userProfileSchema,
}),
z.object({
success: z.literal(false),
error: z.object({
code: z.string(),
message: z.string(),
}),
}),
]);
合成型の構造図
mermaidflowchart TD
compose[合成型] --> union[Union型]
compose --> intersection[Intersection型]
compose --> combination[組み合わせ]
union --> |選択| payment[決済方法]
union --> |条件分岐| response[APIレスポンス]
intersection --> |結合| audit[監査可能なユーザー]
intersection --> |拡張| enhanced[拡張された型]
combination --> |組み立て| profile[ユーザープロフィール]
combination --> |再利用| modular[モジュラー設計]
合成型により、コードの再利用性と保守性が大幅に向上します。基本的な要素を組み合わせることで、複雑なビジネスロジックを型安全に表現できるのです。
効果:データ変換と検証の拡張
効果は、Zod の最も強力な機能の一つです。単純な検証を超えて、データの変換、型の自動変換、カスタム検証ロジックを実装できます。
transform:データの変換処理
transform メソッドを使用することで、検証と同時にデータの変換を行えます。
typescript// 文字列を数値に変換
const stringToNumberSchema = z.string().transform((str) => {
return parseInt(str, 10);
});
// 日付文字列をDateオブジェクトに変換
const dateTransformSchema = z
.string()
.transform((dateStr) => {
return new Date(dateStr);
});
より複雑な変換処理も可能です。
typescript// オブジェクトの構造変換
const apiUserToInternalUserSchema = z
.object({
user_name: z.string(),
user_email: z.string().email(),
user_age: z.number(),
})
.transform((apiUser) => ({
name: apiUser.user_name,
email: apiUser.user_email,
age: apiUser.user_age,
id: crypto.randomUUID(), // 新しいフィールドを追加
}));
配列に対する変換も効果的です。
typescript// CSV文字列を配列に変換
const csvToArraySchema = z.string().transform((csv) => {
return csv.split(',').map((item) => item.trim());
});
// 検証と変換を組み合わせ
const validatedArraySchema = csvToArraySchema.transform(
(arr) => {
return arr.filter((item) => item.length > 0);
}
);
coerce:型の自動変換
coerce は、JavaScript 特有の型変換を安全に行うための機能です。
typescript// 文字列を数値に強制変換
const coercedNumberSchema = z.coerce.number();
// 実行例
console.log(coercedNumberSchema.parse('123')); // 123 (数値)
console.log(coercedNumberSchema.parse(true)); // 1 (数値)
日付の変換では特に有用です。
typescript// 様々な形式を Date に変換
const coercedDateSchema = z.coerce.date();
// これらすべてがDateオブジェクトになる
coercedDateSchema.parse('2024-01-01'); // Date オブジェクト
coercedDateSchema.parse(1704067200000); // Date オブジェクト
coercedDateSchema.parse(new Date()); // Date オブジェクト
真偽値の変換も直感的です。
typescript// 文字列を真偽値に変換
const coercedBooleanSchema = z.coerce.boolean();
console.log(coercedBooleanSchema.parse('true')); // true
console.log(coercedBooleanSchema.parse('false')); // false
console.log(coercedBooleanSchema.parse('')); // false
console.log(coercedBooleanSchema.parse('1')); // true
refine:カスタム検証ロジック
refine メソッドでは、独自の検証ルールを追加できます。
typescript// パスワードの複雑性チェック
const passwordSchema = z
.string()
.min(8, 'パスワードは8文字以上である必要があります')
.refine(
(password) => /[A-Z]/.test(password),
'大文字を含む必要があります'
)
.refine(
(password) => /[0-9]/.test(password),
'数字を含む必要があります'
);
オブジェクト全体に対する検証も可能です。
typescript// 期間の妥当性チェック
const eventSchema = z
.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
title: z.string(),
})
.refine(
(event) =>
new Date(event.startDate) < new Date(event.endDate),
{
message: '開始日は終了日より前である必要があります',
path: ['endDate'], // エラーの対象フィールドを指定
}
);
非同期検証にも対応しています。
typescript// データベースでの重複チェック
const uniqueEmailSchema = z
.string()
.email()
.refine(async (email) => {
// データベースでの検索をシミュレート
const existingUser = await checkEmailExists(email);
return !existingUser;
}, 'このメールアドレスは既に使用されています');
効果システムの処理フロー
mermaidsequenceDiagram
participant Input as 入力データ
participant Validate as 基本検証
participant Transform as transform
participant Coerce as coerce
participant Refine as refine
participant Output as 出力データ
Input ->> Validate: データ受信
Validate ->> Coerce: 型変換
Coerce ->> Transform: データ変換
Transform ->> Refine: カスタム検証
Refine ->> Output: 最終データ
Note over Input, Output: エラーが発生した場合はここで処理が停止
効果を組み合わせることで、非常に柔軟で強力なデータ処理パイプラインを構築できます。入力から出力まで、一貫した型安全性を保ちながら、複雑な変換と検証を実現できるのです。
実践例:プリミティブから効果まで一貫した使い方
これまでに学んだプリミティブ型、合成型、効果を組み合わせて、実際の Web アプリケーションで使用される複雑なフォームバリデーションシステムを構築してみましょう。
ユーザー登録フォームの完全実装
まず、プリミティブ型から始めて段階的に構築していきます。
typescriptimport { z } from 'zod';
// 1. プリミティブ型の定義
const emailSchema = z
.string()
.email('有効なメールアドレスを入力してください');
const passwordSchema = z
.string()
.min(8, 'パスワードは8文字以上必要です');
const ageSchema = z
.number()
.min(13, '13歳以上である必要があります');
次に、合成型を使用してより複雑な構造を作成します。
typescript// 2. 合成型の構築
const addressSchema = z.object({
country: z.string(),
prefecture: z.string(),
city: z.string(),
street: z.string(),
zipCode: z
.string()
.regex(
/^\d{3}-\d{4}$/,
'郵便番号は123-4567の形式で入力してください'
),
});
const personalInfoSchema = z.object({
firstName: z.string().min(1, '名前を入力してください'),
lastName: z.string().min(1, '苗字を入力してください'),
birthDate: z.string().datetime(),
gender: z.enum([
'male',
'female',
'other',
'prefer_not_to_say',
]),
});
そして、効果を適用して高度な処理を実装します。
typescript// 3. 効果の適用
const userRegistrationSchema = z
.object({
// 基本情報
email: emailSchema,
password: passwordSchema,
passwordConfirm: z.string(),
// 個人情報(変換付き)
personal: personalInfoSchema.transform((data) => ({
...data,
fullName: `${data.lastName} ${data.firstName}`,
age: calculateAge(new Date(data.birthDate)),
})),
// 住所(オプショナル)
address: addressSchema.optional(),
// 利用規約同意(型強制)
agreeToTerms: z.coerce.boolean(),
// 年齢(計算フィールド)
calculatedAge: z.number().optional(),
})
// パスワード確認の検証
.refine(
(data) => data.password === data.passwordConfirm,
{
message: 'パスワードが一致しません',
path: ['passwordConfirm'],
}
)
// メール重複チェック
.refine(
async (data) => {
const exists = await checkEmailExists(data.email);
return !exists;
},
{
message: 'このメールアドレスは既に登録されています',
path: ['email'],
}
)
// 最終的なデータ変換
.transform((data) => ({
...data,
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
isEmailVerified: false,
}));
エラーハンドリングと型推論
完全な型安全性を保ちながらエラーハンドリングを実装します。
typescript// 型の推論
type UserRegistrationInput = z.input<
typeof userRegistrationSchema
>;
type UserRegistrationOutput = z.output<
typeof userRegistrationSchema
>;
// フォーム送信処理
async function handleUserRegistration(formData: unknown) {
try {
// 検証と変換の実行
const validatedData =
await userRegistrationSchema.parseAsync(formData);
// データベースへの保存
const savedUser = await saveUserToDatabase(
validatedData
);
return {
success: true,
data: savedUser,
};
} catch (error) {
if (error instanceof z.ZodError) {
// Zodのエラーを整形
const formattedErrors = error.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
code: issue.code,
}));
return {
success: false,
errors: formattedErrors,
};
}
// その他のエラー
throw error;
}
}
API 統合での活用
API エンドポイントでの完全な活用例を見てみましょう。
typescript// APIリクエスト/レスポンスの定義
const apiRequestSchema = z.object({
action: z.enum(['create', 'update', 'delete']),
data: userRegistrationSchema,
metadata: z
.object({
clientVersion: z.string(),
timestamp: z.coerce.date(),
})
.optional(),
});
const apiResponseSchema = z.union([
z.object({
success: z.literal(true),
data: z.object({
id: z.string().uuid(),
email: z.string().email(),
createdAt: z.string().datetime(),
}),
}),
z.object({
success: z.literal(false),
error: z.object({
code: z.string(),
message: z.string(),
details: z
.array(
z.object({
field: z.string(),
message: z.string(),
})
)
.optional(),
}),
}),
]);
実践例の全体フロー
mermaidflowchart TD
input[フォーム入力] --> primitive[プリミティブ検証]
primitive --> compose[合成型構築]
compose --> effect[効果適用]
effect --> transform[データ変換]
effect --> coerce[型強制]
effect --> refine[カスタム検証]
transform --> output[最終データ]
coerce --> output
refine --> output
output --> api[API送信]
api --> response[レスポンス処理]
primitive --> |エラー| error[エラーハンドリング]
compose --> |エラー| error
effect --> |エラー| error
error --> user[ユーザーへの表示]
この実践例では、単純なプリミティブ型から始まり、合成型で構造を構築し、効果で高度な処理を実装する流れを示しています。実際のアプリケーションでも、この段階的なアプローチにより、保守性と型安全性を両立させた堅牢なシステムを構築できます。
まとめ
本記事では、Zod の全体像を「プリミティブ型」→「合成型」→「効果」という 3 つの段階に分けて解説してまいりました。
学習した内容の振り返り
プリミティブ型では、Zod の基礎となる型定義を学びました。文字列、数値、真偽値といった基本型から、配列やオブジェクトの基本構造まで、型安全な検証の土台を理解できました。
合成型では、複雑なデータ構造の構築方法を習得しました。オブジェクトスキーマの組み合わせや、Union・Intersection 型の活用により、実際のアプリケーションで使用される複雑なデータモデルを表現する技術を身につけました。
効果では、データ変換と検証の拡張について深く学びました。transform、coerce、refine の 3 つの機能により、単純な検証を超えた高度なデータ処理が可能になることを理解しました。
Zod を使用する際のベストプラクティス
これらの学習を踏まえて、以下のポイントを意識して開発を進めることをお勧めいたします。
- 段階的な構築: プリミティブ型から始めて、必要に応じて合成型、効果を追加する
- 再利用可能な設計: 基本的なスキーマ要素を定義し、組み合わせて使用する
- 適切なエラーハンドリング: Zod のエラー情報を活用して、ユーザーフレンドリーなメッセージを提供する
- 型推論の活用:
z.infer
を使用して、TypeScript の型を自動生成する
今後の発展
Zod をマスターすることで、TypeScript での開発における型安全性が大幅に向上します。フォームバリデーション、API 通信、データベースとの連携など、あらゆる場面でその力を発揮することでしょう。
継続的な学習により、より効率的で保守性の高いアプリケーション開発が実現できます。ぜひ実際のプロジェクトで Zod を活用し、その利便性を体感してください。
関連リンク
- article
Zod 全体像を図解で理解:プリミティブ → 合成 → 効果(transform/coerce/refine)の流れ
- article
Zod で非同期バリデーション(async)を実装する方法
- article
Zod で配列・オブジェクトを安全に扱うバリデーションテクニック
- article
【実践】Zod の union・discriminatedUnion を使った柔軟な型定義
- article
Zod で条件付きバリデーションを実装する方法(if/then/else パターン)
- article
【徹底解説】Zod の refine と superRefine の違いと実践活用シーン
- article
gpt-oss の全体像と導入判断フレーム:適用領域・制約・成功条件を一挙解説
- article
【解決策】Vitest HMR 連携でテストが落ちる技術的原因と最短解決
- article
【解決策】GPT-5 構造化出力が崩れる問題を直す:JSON モード/スキーマ厳格化の実践手順
- article
【解決】Vite で「Failed to resolve import」が出る原因と対処フローチャート
- article
TypeScript ランタイム検証ライブラリ比較:Zod / Valibot / typia / io-ts の選び方
- article
Emotion 完全理解 2025:CSS-in-JS の強み・弱み・採用判断を徹底解説
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来