3. チェック・リファインメント・パイプライン・双方向コーデック

Zod v4 は、型検査後の制約バリデーションおよびインプレース値変換を合成可能な $ZodCheck<T> ディスクリプタ(inst._zod.def.checks)として統合し、多段階の型変換と双方向シリアライゼーションを ZodPipe<A, B>、ZodTransform<O, I>、ZodCodec<A, B> によってモデル化します。

03_checks_refinements_codecs_1
$ZodCheck ディスクリプタ・リファインメント・パイプライン・双方向コーデックのアーキテクチャ
Source Code
from drawlib import canvas
from drawlib.diagrams.architecture import ArchitectureDiagram, Node, NodeGroup, PhosphorIcon
from drawlib.styles import Styles

canvas.clear()
canvas.setup(width=188, height=96, dpi=150)

d = ArchitectureDiagram(
    node_style=Styles.Primary,
    node_text_style=Styles.DarkBold.patch(text_size=10.0),
    edge_style=Styles.DarkBold,
    edge_text_style=Styles.Dark.patch(text_size=10.0),
    node_card_style=Styles.Neutral,
    title="$ZodCheck ディスクリプタ・リファインメント・ZodPipe / ZodCodec 構造",
)

# Left group: $ZodCheck Descriptors (bottom-left at (0, 0), node width=46 -> center x = 5 + 23 = 28)
checks_grp = d.add(
    NodeGroup(title="1. $ZodCheck<T> ディスクリプタ", padding=5.0),
    xy=(0.0, 0.0),
)
chk_bounds = checks_grp.add(
    Node("境界・サイズチェック\nminLength / maxLength / length\ngt / gte / lt / lte / multipleOf", width=46, height=17, icon=PhosphorIcon.RULER, icon_size=6.0, card_style=Styles.Neutral),
    xy=(28.0, 56.0),
)
chk_format = checks_grp.add(
    Node("形式・プロパティチェック\nregex / lowercase / uppercase\nstartsWith / property / mime", width=46, height=17, icon=PhosphorIcon.MAGNIFYING_GLASS, icon_size=6.0, card_style=Styles.Neutral),
    xy=(28.0, 34.5),
)
chk_mut = checks_grp.add(
    Node("インプレース上書きチェック\ntrim / toLowerCase / toUpperCase\nnormalize / slugify / overwrite(fn)", width=46, height=17, icon=PhosphorIcon.MAGIC_WAND, icon_size=6.0, card_style=Styles.PrimaryNeutral),
    xy=(28.0, 13.0),
)

# Middle group: Schema Attachment & Refinements (node width=36 -> center x = 5 + 18 = 23)
attach_grp = d.add(
    NodeGroup(title="2. スキーマ接続・リファイン", padding=5.0),
    xy=(66.0, 0.0),
)
schema_node = attach_grp.add(
    Node("ZodType<O, I>\n.check(...checks)\n.with(...checks)", width=36, height=20, icon=PhosphorIcon.CUBE, icon_size=6.5, style=Styles.WhiteBold, card_style=Styles.PrimaryFlat, text_style=Styles.WhiteBold.patch(text_size=10.5)),
    xy=(23.0, 52.0),
)
refine_node = attach_grp.add(
    Node("カスタムリファインメント\n.refine(fn, params)\n.superRefine(fn)", width=36, height=20, icon=PhosphorIcon.SHIELD_CHECK, icon_size=6.5, card_style=Styles.PrimaryNeutral),
    xy=(23.0, 15.0),
)

# Right group: Pipelines & Bidirectional Codecs (node width=48 -> center x = 5 + 24 = 29)
pipe_grp = d.add(
    NodeGroup(title="3. パイプライン・コーデック", padding=5.0),
    xy=(122.0, 0.0),
)
pipe_node = pipe_grp.add(
    Node("ZodPipe & ZodTransform\n.pipe(target) / .transform(fn)\nz.preprocess(fn, schema)", width=48, height=20, icon=PhosphorIcon.ARROWS_SPLIT, icon_size=6.5, card_style=Styles.Neutral),
    xy=(29.0, 52.0),
)
codec_node = pipe_grp.add(
    Node("ZodCodec<In, Out>\nz.codec(in, out, {decode, encode})\nz.invertCodec(codec)", width=48, height=20, icon=PhosphorIcon.ARROWS_LEFT_RIGHT, icon_size=6.5, style=Styles.WhiteBold, card_style=Styles.AccentFlat, text_style=Styles.WhiteBold.patch(text_size=10.0)),
    xy=(29.0, 15.0),
)

# Connections
d.connect(chk_bounds, schema_node, padding=1.5)
d.connect(chk_format, schema_node, padding=1.5)
d.connect(chk_mut, schema_node, padding=1.5)
d.connect(refine_node, schema_node, label="追加", padding=1.5)
schema_node.fork([pipe_node, codec_node], at_x=117.0, padding=1.5)

d.draw(xy=(94.0, 46.0), align="center")
canvas.save()

methodZodType.check / refine / superRefine#

すべての ZodType(および ZodMiniType)インスタンスは、schema.def.checks にバリデーションおよび値変更ディスクリプタのイミュータブル配列を保持します。.check()、.with()、.refine()、.superRefine() を呼び出すと、レシーバを変更せずに新しい $ZodCheck インスタンスを追加したクローンスキーマを返します。

メソッドシグネチャ一覧#

メソッド / シグネチャ 戻り値型 説明
schema.check(...checks: (CheckFn<O> | $ZodCheck<O>)[]) this スキーマを複製し、1つ以上の $ZodCheck インスタンスまたは低レベル CheckFn<O> コールバック((payload: ParsePayload<O>) => MaybeAsync<void>)を追加します。
schema.with(...checks: (CheckFn<O> | $ZodCheck<O>)[]) this schema.check(...) のエイリアス(プロトタイプ上で同一の関数参照を共有)。
schema.refine<Ch>(check: Ch, params?: string | $ZodCustomParams) this(または絞り込まれた this & ZodType<R, I>) 真偽値または型述語によるリファインメントを追加します。check が TypeScript の型ガード (val): val is R の場合、出力型を R に絞り込みます。
schema.superRefine(fn: (arg: O, ctx: RefinementCtx<O>) => MaybeAsync<void>, params?: $ZodSuperRefineParams) this ctx.addIssue({ code, message, path, ... }) を通じて複数の任意の Issue を追加できる高度なリファインメントを登録します。

リファインメントパラメータ ($ZodCustomParams)#

オプション 型 デフォルト 説明
error string | $ZodErrorMap<$ZodIssueCustom> undefined リファインメント失敗時に評価されるカスタムエラーメッセージ文字列または関数 (issue) => string。
path PropertyKey[] [] Issue の path に追加される相対プロパティパス(複数フィールド間の相関チェックに有用)。
abort boolean false true の場合、発行される Issue に continue: false を設定し、同一スキーマの後続チェックをスキップします。
when (payload: ParsePayload) => boolean undefined 先行チェックで非中断 Issue が発生していてもこのチェックを実行するかどうかを制御する述語関数。
params Record<string, any> undefined 多言語化(i18n)や UI 側での検査用に $ZodIssueCustom.params に付与される任意のメタデータ辞書。
import * as z from "zod";

const PasswordConfirmSchema = z
  .object({
    password: z.string().min(12),
    confirm: z.string(),
  })
  .refine((data) => data.password === data.confirm, {
    error: "パスワードが一致しません",
    path: ["confirm"],
  });

const AuditLogSchema = z.string().superRefine((val, ctx) => {
  if (!val.startsWith("EVT_")) {
    ctx.addIssue({
      code: "custom",
      message: "EVT_ プレフィックスで始まる必要があります",
      params: { severity: "error" },
    });
  }
  if (val.length > 64) {
    ctx.addIssue({
      code: "too_big",
      origin: "string",
      maximum: 64,
      inclusive: true,
      message: "イベントコードは64文字以内である必要があります",
    });
  }
});

functionz.check & Check Factories#

トップレベルのチェックファクトリ関数は、zod と zod/mini の両方で schema.check(...) に渡して再利用できるスタンドアロンの $ZodCheck<T> ディスクリプタを生成します。

スタンドアロン・チェックファクトリ一覧#

ファクトリ関数 対象型 発行される Issue コード 説明
z.check<O>(fn: CheckFn<O>) 任意の O カスタム(payload.issues.push(...)) 低レベルコールバック (payload: ParsePayload<O>) => MaybeAsync<void> を $ZodCheck<O> にラップします。
z.refine<T>(fn, params?) 任意の T "custom" .refine(fn, params) と等価なスタンドアロンファクトリ関数。
z.superRefine<T>(fn, params?) 任意の T 任意の $ZodIssueCode .superRefine(fn, params) と等価なスタンドアロンファクトリ関数。
z.lt(value, params?) / z.lte(value, params?) number | bigint | Date "too_big" input < value(inclusive: false)または input <= value(inclusive: true)を検証します。
z.gt(value, params?) / z.gte(value, params?) number | bigint | Date "too_small" input > value(inclusive: false)または input >= value(inclusive: true)を検証します。
z.positive(params?) / z.negative(params?) number | bigint "too_small" / "too_big" z.gt(0) および z.lt(0) のショートハンド。
z.nonnegative(params?) / z.nonpositive(params?) number | bigint "too_small" / "too_big" z.gte(0) および z.lte(0) のショートハンド。
z.multipleOf(divisor, params?) number | bigint "not_multiple_of" 浮動小数点安全な剰余演算または BigInt 剰余により割り切れるかを検証します。
z.minLength(min, params?) / z.maxLength(max, params?) / z.length(len, params?) { length: number } (string, Array, File) "too_small" / "too_big" .length の境界を検証します(z.length(len) では exact: true)。
z.minSize(min, params?) / z.maxSize(max, params?) / z.size(len, params?) { size: number } (Map, Set, File) "too_small" / "too_big" .size の境界を検証します(z.size(len) では exact: true)。
z.regex(pattern, params?) string "invalid_format" (format: "regex") 正規表現 RegExp に対する一致を検証します。
z.lowercase(params?) / z.uppercase(params?) string "invalid_format" 文字列がすべて小文字または大文字であることを検証します。
z.includes(substr, params?) / z.startsWith(prefix, params?) / z.endsWith(suffix, params?) string "invalid_format" 部分文字列の包含、プレフィックス、サフィックスを検証します。
z.property(prop, schema, params?) 任意のオブジェクト / 値 schema から委譲 input[prop] の値を schema で検証します(例: .length やカスタム getter の検証)。
z.properties(shape, params?) クラスインスタンス shape[k] から委譲 z.instanceof(Cls) スキーマ上で複数のインスタンスプロパティを検証します。
z.mime(types: string | string[], params?) File "invalid_value" file.type が許可された MIME タイプ一覧に含まれるか検証します。
z.overwrite(fn: (val: T) => T) 任意の T (なし — payload.value を変更) スキーマの静的型を変更せずに payload.value = fn(payload.value) をインプレースで書き換えます。
z.trim() / z.toLowerCase() / z.toUpperCase() / z.normalize(form?) / z.slugify() string (なし — payload.value を変更) 文字列正規化のための組み込み $ZodCheckOverwrite ディスクリプタ群。
import * as z from "zod";

// Classic と Mini の両方で再利用できるチェック配列
const nonEmptyTrimmed = [z.trim(), z.minLength(1, "空文字は許可されていません")] as const;

const TitleSchema = z.string().check(...nonEmptyTrimmed, z.maxLength(120));
const UploadSchema = z.file().check(
  z.minSize(1024),
  z.maxSize(5 * 1024 * 1024),
  z.mime(["image/png", "image/webp"]),
);

functionz.string Check & Transform Methods#

Classic zod の ZodString は、基底の $ZodCheck ファクトリをラップするメソッドチェーンと、集約されたメタデータ getter(.minLength、.maxLength、.format)を提供します。

ZodString メソッド一覧#

メソッド 基底の $ZodCheck 値の変更 説明
.min(minLength, params?) $ZodCheckMinLength なし str.length >= minLength を要求します。schema.minLength を更新します。
.max(maxLength, params?) $ZodCheckMaxLength なし str.length <= maxLength を要求します。schema.maxLength を更新します。
.length(len, params?) $ZodCheckLengthEquals なし str.length === len を要求します。minLength と maxLength の両方を設定します。
.nonempty(params?) $ZodCheckMinLength(1) なし .min(1, params) と等価です。
.regex(regex, params?) $ZodCheckRegex なし regex.test(str) の成功を要求します。
.includes(value, params?) $ZodCheckIncludes なし str.includes(value) を要求します。
.startsWith(value, params?) $ZodCheckStartsWith なし str.startsWith(value) を要求します。
.endsWith(value, params?) $ZodCheckEndsWith なし str.endsWith(value) を要求します。
.lowercase(params?) $ZodCheckLowerCase なし str === str.toLowerCase() を検証します(値の変換は行いません)。
.uppercase(params?) $ZodCheckUpperCase なし str === str.toUpperCase() を検証します(値の変換は行いません)。
.trim() $ZodCheckOverwrite あり チェックチェーン内の該当位置で前後の空白を str.trim() により除去します。
.toLowerCase() $ZodCheckOverwrite あり str.toLowerCase() により小文字へインプレース変換します。
.toUpperCase() $ZodCheckOverwrite あり str.toUpperCase() により大文字へインプレース変換します。
.normalize(form?) $ZodCheckOverwrite あり str.normalize(form) により Unicode 正規化を行います(デフォルト "NFC"、"NFD"、"NFKC"、"NFKD")。
.slugify() $ZodCheckOverwrite あり 文字列を小文字の URL セーフな ASCII スラッグに変換します。

[!TIP] .trim()、.toLowerCase()、.normalize() はすべて def.checks 内の $ZodCheck ディスクリプタであるため、メソッドチェーンの記述順通りに実行されます。たとえば z.string().trim().min(3) は空白除去後に3文字以上かを検査しますが、z.string().min(3).trim() は空白除去前に文字数を検査します。


functionz.number Bounds & Check Methods#

ZodNumber および ZodBigInt は、$ZodCheckGreaterThan、$ZodCheckLessThan、$ZodCheckMultipleOf、$ZodCheckNumberFormat に基づく境界・符号・倍数チェックメソッドを提供します。

ZodNumber メソッド・プロパティ一覧#

メソッド / プロパティ 対応するチェック / 型 説明
.gt(value, params?) z.gt(value, params) より大きい(x > value、JSON Schema の exclusiveMinimum)。
.gte(value, params?) / .min(value, params?) z.gte(value, params) 以上(x >= value、JSON Schema の minimum)。
.lt(value, params?) z.lt(value, params) より小さい(x < value、JSON Schema の exclusiveMaximum)。
.lte(value, params?) / .max(value, params?) z.lte(value, params) 以下(x <= value、JSON Schema の maximum)。
.positive(params?) z.gt(0, params) 正の数(x > 0)。
.nonnegative(params?) z.gte(0, params) 非負の数(x >= 0)。
.negative(params?) z.lt(0, params) 負の数(x < 0)。
.nonpositive(params?) z.lte(0, params) 非正の数(x <= 0)。
.multipleOf(value, params?) / .step(value, params?) z.multipleOf(value, params) 浮動小数点安全な倍数チェック(floatSafeRemainder(x, value) === 0)。
.int(params?) / .safe(params?) $ZodCheckNumberFormat("safeint") 安全な整数範囲(Number.isSafeInteger(x))に制限します。単体の整数スキーマには z.int() の使用を推奨します。
schema.minValue / schema.maxValue number | null 登録された全チェックから集約された最小値・最大値。
schema.format string | null 集約された数値フォーマット("safeint"、"int32"、"uint32"、"float32"、"float64"、または null)。

methodZodType.overwrite#

.overwrite(fn)(およびスタンドアロンの z.overwrite(fn))は、スキーマを ZodPipe でラップしたり TypeScript の静的入出力型を変更したりすることなく、def.checks 配列内でインプレースの値変換を実行します。

.transform() との比較#

項目 schema.overwrite(fn) / z.overwrite(fn) schema.transform(fn)
静的型 (z.input<S> vs z.output<S>) 維持される(Input === Output === T) Output が NewOut に変化(ZodPipe<S, ZodTransform<NewOut, O>>)
内部表現 def.checks に $ZodCheckOverwrite<T> を追加 $ZodTransform を持つ ZodPipe でラップ
JSON Schema 変換互換性 変換可能(z.toJSONSchema() で透過的に処理) "output" モードでは override を指定しない限り変換不可
z.encode() との互換性 順方向(decode)と逆方向(encode)の両方で実行可能 単方向のため z.encode() 時に $ZodEncodeError をスロー
import * as z from "zod";

// JSON Schema や encode() との互換性を保ったままタグ配列を正規化・重複排除・ソート
const TagsSchema = z
  .array(z.string().trim().toLowerCase())
  .overwrite((tags) => Array.from(new Set(tags)).sort());

TagsSchema.parse(["  Zod ", "typescript", "zod"]); // => ["typescript", "zod"]

classZodTransform & ZodPipe#

単方向のデータ変換および複数スキーマを連結するバリデーションパイプラインは、ZodTransform<O, I> と ZodPipe<A, B> によって構成されます。

コンストラクタ・メソッド一覧#

API シグネチャ 戻り値型 説明
schema.transform(fn) transform<NewOut>(fn: (arg: O, ctx: RefinementCtx<O>) => MaybeAsync<NewOut>) ZodPipe<this, ZodTransform<Awaited<NewOut>, O>> this で検証した後、出力値を fn で変換します。ctx.addIssue(...) または z.NEVER の返却でパイプラインを中断できます。
z.transform(fn) z.transform<I = unknown, O = I>(fn: (input: I, ctx: ParsePayload) => O) ZodTransform<Awaited<O>, I> 入力スキーマを持たないスタンドアロンの変換ノードを作成します。
schema.pipe(target) pipe<T extends $ZodType<any, O>>(target: T) ZodPipe<this, T> this(上流 in スキーマ)の出力 output<this> を target(下流 out スキーマ)の入力 input<T> に接続します。
z.pipe(inSchema, outSchema) z.pipe<A, B>(in_: A, out: B) ZodPipe<A, B> ZodPipe の関数型コンストラクタ(zod および zod/mini の両方で利用可能)。
pipeSchema.in / pipeSchema.out ZodPipe<A, B> の読み取り専用プロパティ A / B 上流(in)および下流(out)スキーマへの直接参照。
import * as z from "zod";

const CsvIntsSchema = z
  .string()
  .transform((s) => s.split(",").map((part) => part.trim()))
  .pipe(z.array(z.coerce.number().int()));

type In = z.input<typeof CsvIntsSchema>;   // string
type Out = z.output<typeof CsvIntsSchema>; // number[]

classZodCodec & z.codec#

Zod v4 で導入された ZodCodec<A, B> は ZodPipe<A, B> を拡張し、双方向の変換関数ペア(decode と encode)を備えることで、単一のスキーマ定義による型安全なデシリアライズとシリアライズを実現します。

03_checks_refinements_codecs_2
ZodCodec における順方向 (Decode) と逆方向 (Encode) の双方向実行フロー
Source Code
from drawlib import canvas
from drawlib.diagrams.sequence import Participant, ParticipantGroup, PhosphorIcon, SequenceDiagram
from drawlib.styles import Styles

canvas.clear()
canvas.setup(width=166, height=106, dpi=150)

d = SequenceDiagram(
    node_style=Styles.Primary,
    node_text_style=Styles.DarkBold.patch(text_size=10.5),
    edge_style=Styles.DarkBold,
    edge_text_style=Styles.Dark.patch(text_size=10.0),
    node_card_style=Styles.Neutral,
    title="ZodCodec<InSchema, OutSchema> — 順方向 Decode と逆方向 Encode",
    autonumber=True,
    col_width=36.0,
    step_y=6.5,
)

caller = d.add(Participant("呼び出し元アプリ", width=22, height=13, icon=PhosphorIcon.TERMINAL_WINDOW, icon_size=6.0, card_style=Styles.Neutral))

codec_grp = d.add(ParticipantGroup(title="ZodCodec<InSchema, OutSchema>", padding=4.0, style=Styles.Neutral))
in_schema = codec_grp.add(Participant("inSchema\n(A: ワイヤ型)", width=24, height=13, icon=PhosphorIcon.FILE_TEXT, icon_size=6.0, card_style=Styles.Neutral))
xform = codec_grp.add(Participant("Codec ブリッジ\n{decode, encode}", width=26, height=13, icon=PhosphorIcon.ARROWS_LEFT_RIGHT, icon_size=6.0, style=Styles.WhiteBold, card_style=Styles.PrimaryFlat, text_style=Styles.WhiteBold.patch(text_size=10.5)))
out_schema = codec_grp.add(Participant("outSchema\n(B: ドメイン型)", width=24, height=13, icon=PhosphorIcon.CUBE, icon_size=6.0, card_style=Styles.PrimaryNeutral))

with d.alt("順方向 (decode / parse)") as blk:
    blk.style = Styles.Neutral.patch(line_style="dashed")
    caller.request(in_schema, "ワイヤ入力検証 (A._zod.run)")
    in_schema.request(xform, "decode(output<A>, payload)")
    xform.request(out_schema, "input<B> 検証・パイプチェック")
    out_schema.reply(caller, "output<B> を返却 (ドメイン値)", style=Styles.Success, text_style=Styles.Success.patch(text_size=10.0))

    d.else_("逆方向 (encode / safeEncode)")
    caller.request(out_schema, "ドメイン値検証 (B._zod.run)")
    out_schema.request(xform, "encode(input<B>, payload)")
    xform.request(in_schema, "output<A> 検証 (A._zod.run)")
    in_schema.reply(caller, "input<A> を返却 (ワイヤ値)", style=Styles.Accent, text_style=Styles.Accent.patch(text_size=10.0))

d.draw(xy=(83.0, 51.0), align="center")
canvas.save()

z.codec & z.invertCodec シグネチャ一覧#

関数 / メソッド シグネチャ 戻り値型 説明
z.codec(in_, out, { decode, encode }) codec<A, B>(in_: A, out: B, params: { decode: (val: output<A>, payload: ParsePayload<output<A>>) => MaybeAsync<input<B>>; encode: (val: input<B>, payload: ParsePayload<input<B>>) => MaybeAsync<output<A>> }) ZodCodec<A, B> ワイヤスキーマ A とドメインスキーマ B の間の双方向コーデックを生成します。
z.invertCodec(codec) invertCodec<A, B>(codec: ZodCodec<A, B>) ZodCodec<B, A> in と out を入れ替え、decode と encode を反転させた新しい ZodCodec<B, A> を返します。
z.stringbool(params?) stringbool(params?: string | $ZodStringBoolParams) ZodCodec<ZodString, ZodBoolean> 環境変数文字列("true", "1", "yes", "on" 対 "false", "0", "no", "off")と boolean を相互変換する組み込みコーデック。
import * as z from "zod";

const IsoDateCodec = z.codec(z.iso.datetime(), z.date(), {
  decode: (isoString) => new Date(isoString),
  encode: (dateObj) => dateObj.toISOString(),
});

// 順方向 (decode): string -> Date
const parsedDate = z.decode(IsoDateCodec, "2026-03-30T12:00:00.000Z");

// 逆方向 (encode): Date -> string
const wireString = z.encode(IsoDateCodec, new Date("2026-03-30T12:00:00.000Z"));

// コーデックの反転: Date -> string (順方向), string -> Date (逆方向)
const DateToIsoCodec = z.invertCodec(IsoDateCodec);

functionz.preprocess#

z.preprocess(fn, schema) は、入力値を schema に渡す前に fn(input, ctx) を実行する ZodPreprocess<U, B>(ZodPipe<ZodTransform<unknown, B>, U> の特殊化クラス)を構築します。

シグネチャと移行ガイド#

関数 シグネチャ 推論される型 (z.input / z.output) 説明
z.preprocess(fn, schema) preprocess<A, U extends SomeType, B = unknown>(fn: (arg: B, ctx: RefinementCtx) => A, schema: U): ZodPreprocess<U, B> input: B(デフォルト unknown)、output: z.output<U> 未検証の生入力を fn で前処理してから schema を実行します。z.toJSONSchema() では透過的に schema に委譲されます。
import * as z from "zod";

// 入力型が既知の場合は z.pipe() または z.codec() を推奨し、未知の入力の前処理には z.preprocess() を使用します
const TrimmedNumber = z.preprocess(
  (val) => (typeof val === "string" ? val.trim() : val),
  z.coerce.number().int(),
);