## はじめに
[[Zod]] v3を使うといつも使い方を忘れてしまうので、重要なポイントに絞り、動作確認をしてみたレポート。
### 環境
| 対象 | バージョン |
| -------------- | ------- |
| [[Zod]] | 3.25.76 |
| [[Deno]] | 2.5.6 |
| [[TypeScript]] | 5.9.2 |
## v4で非推奨になるもの
使わない方がいいものを最初に頭に入れておく。結論から言うと気にしなくていい。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://zod.dev/icon.png?39fe259ddd7f4224" />
<span class="link-card-v2-site-name">Zod</span>
</div>
<div class="link-card-v2-title">
Migration guide | Zod
</div>
<div class="link-card-v2-content">
Complete changelog and migration guide for upgrading from Zod 3 to Zod 4
</div>
<img class="link-card-v2-image" src="https://zod.dev/og.png?title=Migration%20guide&description=Complete%20changelog%20and%20migration%20guide%20for%20upgrading%20from%20Zod%203%20to%20Zod%204&path=zod.dev%2Fv4%2Fchangelog" />
<a href="https://zod.dev/v4/changelog"></a>
</div>
### メソッド
`invalid_type_error` と `required_error` だけは注意だが、v3時点で利用しないという選択肢はとれないので気にしなくていい。
| メソッド | 対応方針 | 備考 |
| -------------------- | ------------ | ---------------------- |
| `invalid_type_error` | そのまま使う | v4の推奨方式はv3にない |
| `required_error` | そのまま使う | v4の推奨方式はv3にない |
| `errorMap` | 使わない | |
| `formErrors` | 使わない | |
| `string().ip` | 使わない | |
| `string().ipv6` | 使わない | |
| `string().cidr` | 使わない | |
| `nonstrict` | 使わない | |
| `deepPartial` | 使わない | |
| `ostring` | 使わない | |
| `onumber` | 使わない | |
| `create` | 使わない | |
### クラス
呼び出すわけではないので、型定義を利用しなければ問題ないはず。
| クラス | 対応方針 | 備考 |
| --------------- | ---- | ------------------- |
| `ZodEffects` | 使わない | |
| `ZodPreprocess` | 使わない | `preprocess` は使ってOK |
| `ZodBranded` | 使わない | |
## 基本
```ts
import * as z from "zod";
// スキーマの作成
const schema = z.object({
name: z.string(),
});
// 推論した型を定義
type Animal = z.infer<typeof schema>;
// パースしてvalidationや変換
const r = schema.parse({ name: "dog" });
console.log(r);
// Output: { name: 'dog' }
```
> [!caution]
> `transform` などで変換が必要な場合は `z.infer` ではなく `z.input` と `z.output` を使う。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://v3.zod.dev/static/favicon-32x32.png" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
TypeScript-first schema validation with static type inference
</div>
<div class="link-card-v2-content">
TypeScript-first schema validation with static type inference
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/1cac1150838995e1f7d1643c00eee51a5d884f2054f995c9d3225b07b0eddb39/colinhacks/zod" />
<a href="https://v3.zod.dev/?id=basic-usage"></a>
</div>
## スキーマ定義
### 必須 or 任意
#### デフォルトは必須
[[プリミティブ型 (JavaScript)|プリミティブ型]]が指定されているときは『必須』である。
```ts
z.object({
key: z.string()
})
```
これは以下と同じだと考えれば分かりやすい。
```ts
{
key: string
}
```
#### 任意にする
`{ key: T }` に対して任意にしたい場合。
- `.optional()`: `{ key?: T | undefined }`
- `.nullable()`: `{ key: T | null }`
- `.nullish()`: `{ key?: T | undefined | null }`
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=primitives)
`z.optional(z.string())` のようにwrapすることもできる。
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=optionals)
#### 必須にする
`.unwrap` を使う。
```ts
const schema = z.object({
opt: z.string().optional().unwrap(),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { opt: string; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=optionals)
### キャスト
`.coerce` を使う。
```ts
const schema = z.object({
forceString: z.coerce.string(),
});
const r = schema.parse({ forceString: 123 });
console.log(r);
// Output: { forceString: '123' }
```
ただ、[[undefined (JavaScript)|undefined]]などもそのまま文字列になるので注意。
```ts
const schema = z.object({
forceString: z.coerce.string(),
});
const r = schema.parse({ hoge: 123 });
console.log(r);
// Output: { forceString: 'undefined' }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=coercion-for-primitives)
### [[リテラル型 (TypeScript)|リテラル型]]
```ts
const schema = z.object({
pet: z.literal("タツヲ"),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { pet: "タツヲ"; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=literals)
### [[ユニオン型 (TypeScript)|ユニオン型]]
文字列の[[ユニオン型 (TypeScript)|ユニオン型]]。
```ts
const schema = z.object({
animal: z.enum(["cat", "dog", "parrot"]),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { animal: "cat" | "dog" | "parrot"; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=zod-enums)
### 文字数の制限
```ts
const schema = z.object({
str: z.string().min(2).max(5), // 2~5文字
str5: z.string().length(5), // 5文字
});
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=strings)
### 数値
```ts
const schema = z.object({
num1: z.number().min(5).max(10), // 5以上10以下
num2: z.number().int(), // 整数 (7.0はOK)
num3: z.number().positive(), // 0より大きい
num4: z.number().nonnegative(), // 0以上
});
```
### 日付
`Z` なしだとエラーになるので注意。
```ts
const schema = z.object({
date: z.string().date(), // 2025-12-11 形式
datetime: z.string().datetime(), // タイムゾーン(Z)付きの日時形式
});
type schema = z.infer<typeof schema>;
// ^? type schema = { date: string; datetime: string; }
const r = schema.parse({
date: "2025-12-11",
datetime: "2025-12-11T10:00Z",
});
console.log(r);
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=strings)
### [[配列型 (TypeScript)|配列型]]
#### 2つの表現方法
```ts
const schema = z.object({
str: z.string(),
}).array();
type schema = z.infer<typeof schema>;
// ^? type schema = { str: string; }[]
```
または
```ts
const schema = z.array(z.object({
str: z.string(),
}));
type schema = z.infer<typeof schema>;
// ^? type schema = { str: string; }[]
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=arrays)
#### 制限
```ts
const schema = z.object({
ary1: z.string().array().min(2).max(4), // サイズが2~4
ary2: z.string().array().length(3), // サイズが3
ary3: z.string().array().nonempty(), // 空配列不可
});
type schema = z.infer<typeof schema>;
// ^? type schema = { ary1: string[]; ary2: string[]; ary3: [string, ...string[]]; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=arrays)
### [[タプル型 (TypeScript)|タプル型]]
```ts
const schema = z.object({
tup: z.tuple([z.string(), z.number()]),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { tup: [string, number]; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=tuples)
### [[ユニオン型 (TypeScript)|ユニオン型]]
```ts
const schema = z.object({
uni: z.union([z.string(), z.number()]),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { uni: string | number; }
```
または
```ts
const schema = z.object({
uni: z.string().or(z.number()),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { uni: string | number; }
```
> [TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=unions)
[[判別されたユニオン型]]の場合は `.discriminatedUnion` を使ったほうがパフォーマンスがいいらしい。
> Such unions can be represented with the `z.discriminatedUnion` method. This enables faster evaluation, because Zod can check the discriminator key (`status` in the example above) to determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod report friendlier errors.
>
> *[TypeScript-first schema validation with static type inference](https://v3.zod.dev/?id=discriminated-unions) *
### [[Record型 (TypeScript)|Record型]]
```ts
const schema = z.object({
record: z.record(z.string(), z.number()),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { record: Record<string, number>; }
```
### 前処理
入力値が [[unknown型]] の前提で、前処理をしてからバリデーションしたりできる。
```ts
const schema = z.object({
record: z.preprocess(
(x) => String(x),
z.string(),
),
});
type input = z.input<typeof schema>;
// ^? type schema = { record: unknown; }
type schema = z.infer<typeof schema>;
// ^? type schema = { record: Record<string, number>; }
```
上記の例は[[#キャスト]]で十分だが、 form周りは型が少々複雑なので正規化する目的で利用する。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" />
<span class="link-card-v2-site-name">Minerva</span>
</div>
<div class="link-card-v2-title">
📰inputタグでbindされる値
</div>
<div class="link-card-v2-content">Vueのv-modelでinputタグtype="number" "date" "datetime-local"にバインドされる値と型を、TypeScriptの型定義やテンプレートリテラル型でのNarrowing観点から整理。</div>
<img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Notes/attachments/report.webp" />
<a data-href="📰inputタグでbindされる値" class="internal-link"></a>
</div>
%%[[📰inputタグでbindされる値]]%%
たとえば `type="number"` の[[inputタグ]]は
- `""` だと値なし
- それ以外は値あり(数値)
となるので
```ts
const schema = z.object({
record: z.preprocess(
(x) => x === "" ? undefined : x,
z.number().optional(),
),
});
type schema = z.infer<typeof schema>;
// ^? type schema = { record?: number | undefined; }
```
> [!question]- `record` が未指定でも `invalid_type` が出る
> `{ "code": "invalid_type" }` となるのは仕様。ただ、
>
> ```ts
> z.number({ required_error: "必須だよ", invalid_type_error: "型が違うよ" })
> ```
>
> のように `required_error` を指定すれば、`{"message"}` だけは出し分けられる。(UIに表示される内容も変わる)
## その他
### `.input` と `.output`
`infer` は実質 `.output` と同じっぽい。基本的に出力に推論したいはずなので、`.infer` を使っておけば問題ないはず。
```ts
export type TypeOf<T extends ZodType<any, any, any>> = T["_output"];
export type input<T extends ZodType<any, any, any>> = T["_input"];
export type output<T extends ZodType<any, any, any>> = T["_output"];
export type { TypeOf as infer };
```
[[Zod]] v4 でも同様。
```ts
export type input<T> = T extends {
_zod: {
input: any;
};
} ? T["_zod"]["input"] : unknown;
export type output<T> = T extends {
_zod: {
output: any;
};
} ? T["_zod"]["output"] : unknown;
export type { output as infer };
```
### [[メソッドチェーン]]とwrap
> [!todo]
>
## メモ
```ts
const ez = {
string: z.string(...)
}
```
みたいなやつを型推論が変わらない範囲でつくればいいのでは?