はじめに
こんにちは。アメリカ在住で独学エンジニアを目指している 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() |
---|---|---|
実行タイミング | 検証前 | 検証後 |
主な目的 | 値を検証できる形に整える | 検証後に値を加工する |
使い道 | 型変換・空文字除外 | 正常値の整形・組み合わせ |
イメージ | 「入力を整える」 | 「出力を整える」 |