## はじめに [[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(...) } ``` みたいなやつを型推論が変わらない範囲でつくればいいのでは?