はじめに
こんにちは。アメリカ在住で独学エンジニアを目指している Taira です。
最近プロジェクトにZodを導入したのですが、全容をあまり理解しておらず以下のような処理をUtilsとして作成しておりました。
-
"42"(文字列)を数値として扱いたい - 空文字
""をundefinedにしたい -
"2025-10-09"(文字列)をDateに変換したい
ですが、そんなことをする必要がなく、Zodの機能を使えば簡単に実現できるんです。
そんなときに活躍するのが z.preprocess() です。
でも実は、Zodには似たような .transform() という機能もあります。
この2つ、似てるけど実は全然違うタイミングで動くんです。
この記事ではその違いと使い分けを、具体例を交えながらわかりやすく解説します!
z.preprocess()とは?
z.preprocess() は バリデーションの「前」に値を整形する 関数です。
const schema = z.preprocess(
(val) => (typeof val === "string" ? Number(val) : val),
z.number()
);
schema.parse("42"); // => 42
ここでは、"42" という文字列を Number("42") で数値に変換してから z.number() に渡しています。
つまり、**「Zodが型チェックする前に値を直す」**のが目的です。
.transform()とは?
一方の .transform() は、バリデーションの「後」に値を加工する関数です。
const schema = z.string().transform((val) => val.trim().toUpperCase());
schema.parse(" hello "); // => "HELLO"
この場合は、まず z.string() で「文字列かどうか」を確認して、
そのあとで .transform() の関数が実行されます。
処理の流れを比較してみよう
const schema = z.preprocess(
(val) => (typeof val === "string" ? Number(val) : val), // ← 前処理
z.number().transform((n) => n * 2) // ← 後処理
);
schema.parse("10"); // => 20
動作の順番はこうなります
-
"10"が渡される -
preprocessによって"10" → 10に変換 -
z.number()でバリデーション(OK) -
.transform()で10 * 2 = 20に変換 - 最終結果:
20
💡 よくある実務パターン
| 目的 | 使うべき関数 | 例 |
|---|---|---|
| 文字列数値を数値に直したい | preprocess |
"42" → 42 |
| 空文字を除外したい | preprocess |
"" → undefined |
| 日付文字列をDateに変換 | preprocess |
"2025-10-09" → new Date() |
| 検証後に文字列を整形 | .transform |
"hello" → "HELLO" |
| 正常値を構造化 | .transform |
{ first, last } → fullName |
⚠️ 注意点
-
preprocessは バリデーション前 に実行されるので、ここで変換した値が不正だとスキーマが通りません。 -
.transformは バリデーション後 に動くため、型安全な値を安心して加工できます。 - 「これをしないとエラーになる」場合は
preprocess、
「検証後に整えたい」場合は.transformを選ぶのが鉄則です。
まとめ
| 項目 | z.preprocess() |
.transform() |
|---|---|---|
| 実行タイミング | 検証前 | 検証後 |
| 主な目的 | 値を検証できる形に整える | 検証後に値を加工する |
| 使い道 | 型変換・空文字除外 | 正常値の整形・組み合わせ |
| イメージ | 「入力を整える」 | 「出力を整える」 |