# Zod

> Zod v4 schema validation 最佳實踐指南。當需要定義 schema、驗證/解析 JSON 資料、type inference、或處理 unknown data 時使用。

- Skill: `recca0120/zod` (Agent Skill)
- Install (CLI): `npx skillmds@latest add recca0120/zod`
- Raw SKILL.md: https://api.skillmd.com/api/skills/recca0120/zod/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: recca0120 (https://skillmd.com/u/recca0120)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/recca0120/zod

---


# Zod v4 Guide (v4.3+)

## 安裝

```bash
npm install zod    # v4 is latest
```

需要 TypeScript 5.5+ 且 `"strict": true`。

## Import

```ts
import { z } from "zod";
```

## Primitives

```ts
z.string()    z.number()    z.boolean()
z.null()      z.undefined() z.unknown()
z.int()       z.int32()     z.float64()
```

## Objects

```ts
// 預設 strip unknown keys
const User = z.object({
  name: z.string(),
  age: z.number(),
});

// 嚴格模式：拒絕 unknown keys
const StrictUser = z.strictObject({ name: z.string() });

// 寬鬆模式：保留 unknown keys
const LooseUser = z.looseObject({ name: z.string() });
```

### Object 操作

```ts
User.extend({ email: z.email() });
// 效能更好的寫法：
z.object({ ...User.shape, email: z.email() });

User.pick({ name: true });
User.omit({ age: true });
User.partial();
User.required();
User.keyof();
```

## Arrays

```ts
z.array(z.string())
z.array(z.string()).min(1).max(10)
```

## Enums

```ts
const Color = z.enum(["red", "green", "blue"]);

// TypeScript enums（v4 新增，取代 z.nativeEnum）
enum Direction { Up, Down }
z.enum(Direction);
```

## Optional / Nullable / Default

```ts
z.string().optional()     // string | undefined
z.string().nullable()     // string | null
z.string().nullish()      // string | null | undefined
z.string().default("hi")  // 預設值
z.number().catch(0)       // parse 失敗時的 fallback
```

## parse vs safeParse

```ts
// 失敗時拋出 ZodError
const data = schema.parse(input);

// 不拋錯，回傳 discriminated union
const result = schema.safeParse(input);
if (result.success) {
  result.data;   // typed
} else {
  result.error;  // ZodError
}
```

## Type Inference

```ts
type User = z.infer<typeof User>;         // output type
type UserInput = z.input<typeof User>;    // input type（transforms 前）
```

**最佳實踐**：從 schema 推導 type（`z.infer`），不要另外定義 interface — single source of truth。

## String Formats（v4 新增 top-level）

```ts
z.email()    z.url()     z.uuid()
z.iso.date() z.iso.datetime()
// 舊寫法 z.string().email() 已 deprecated
```

## Transforms & Pipes

```ts
z.string().transform(val => val.length)  // string → number
z.coerce.number()                         // String(input) → number
```

## Refinements

```ts
z.string().refine(val => val.length <= 255, { error: "Too long" });

// 跨欄位驗證
z.object({
  password: z.string(),
  confirm: z.string(),
}).refine(d => d.password === d.confirm, {
  error: "Passwords don't match",
  path: ["confirm"],
});
```

## Recursive Schemas（v4 用 getter，不需要 z.lazy）

```ts
const Category = z.object({
  name: z.string(),
  get children() { return z.array(Category); },
});
```

## Branded Types

```ts
const UserId = z.string().brand<"UserId">();
type UserId = z.infer<typeof UserId>;
```

## Error 自訂（v4 改用 error 參數）

```ts
// v3: message / invalid_type_error / required_error（已移除）
// v4:
z.string({ error: "Must be string" });
z.string({ error: (issue) =>
  issue.input === undefined ? "Required" : "Invalid"
});
```

## JSON Schema 轉換（v4 新增）

```ts
const jsonSchema = z.toJSONSchema(schema);
```

## 最佳實踐

1. **Single source of truth**：schema 定義一次，type 用 `z.infer` 推導
2. **解析 unknown JSON**：用 `.safeParse()` 處理不信任的外部資料
3. **Unknown keys**：預設 `z.object()` 會 strip — 適合清理舊格式資料
4. **Union 效能**：大量 union 用 `z.discriminatedUnion()`（O(1) lookup）
5. **Composition**：用 spread `{ ...Base.shape }` 優於 `.extend()` chain
6. **Nullable fields**：`.nullable()` 對應 `null`，`.optional()` 對應 `undefined`

