AI 电子伴侣
创建时间: 2026-04-18最后更新: 2026-04-18

1. 基础类型是 Zod 的地基

真实项目里的数据结构看起来都很复杂:嵌套对象、数组、联合类型、可选字段……但你把它们一层层拆下去,最底层全是基础类型(Primitive Types)

  • 用户名是 string
  • 年龄是 number
  • 是否订阅是 boolean
  • 创建时间是 date

在 Zod 里,它们对应的是这 4 个最常用的 schema 构造器:

index.ts
1
import { z } from 'zod'
2
3
z.string()
4
z.number()
5
z.boolean()
6
z.date()

这一篇我们把这 4 个基础类型一个个过一遍。每一个都有一些新手非常容易踩的小坑,我会标出来。

读完这一篇,你写 90% 的 schema 都不会卡在基础类型上。

2. z.string()

z.string() 是最常用、也是附加方法最多的一个。光是用来校验文本数据,它几乎就把你日常能想到的需求全盖了。

先看一组最常见的方法:

index.ts
01
// 长度
02
z.string().min(1) // 至少 1 个字符
03
z.string().max(100) // 最多 100 个字符
04
z.string().length(11) // 严格等于 11 个字符
05
06
// 常用格式
07
z.string().email() // 邮箱
08
z.string().url() // URL
09
z.string().uuid() // UUID
10
z.string().regex(/^\d+$/) // 自定义正则
11
12
// 字符匹配
13
z.string().startsWith('sk-')
14
z.string().endsWith('.com')
15
z.string().includes('@')

2.1 链式组合

Zod 的 API 设计是链式的:每个方法都返回一个新的 schema,你可以继续往后接。

index.ts
1
const ApiKeySchema = z.string()
2
.startsWith('sk-')
3
.min(20)
4
.max(100)

这份 schema 要求字符串必须以 sk- 开头、长度在 20~100 之间。三个规则读起来就像读一句自然语言。

2.2 自带的字符串变换

除了校验,z.string() 还带了几个会修改数据的方法:

index.ts
1
z.string().trim() // 去除前后空白
2
z.string().toLowerCase() // 转小写
3
z.string().toUpperCase() // 转大写

这类方法跟校验方法不同:它们会返回修改后的数据

index.ts
1
const schema = z.string().trim().toLowerCase()
2
console.log(schema.parse(' Hello ')) // 'hello'

所以 parse 之后你拿到的,不一定是原始数据,可能是被 Zod 处理过的版本。这点以后讲 transform 还会再展开。

2.3 日期时间字符串

一个特别容易被忽视的点:z.date() 不吃字符串。如果你要校验的是 ISO 时间字符串(前后端之间传的最多是字符串),应该用:

index.ts
1
z.string().datetime() // 2024-01-01T00:00:00Z
2
z.string().date() // 2024-01-01
3
z.string().time() // 14:30:00

这三个是 z.string() 下的方法,返回值仍然是字符串,只是校验了格式。

3. z.number()

z.number() 用起来和 z.string() 很像,也是一串链式方法:

index.ts
01
// 整数
02
z.number().int()
03
04
// 范围
05
z.number().min(0).max(100)
06
z.number().gt(0) // > 0
07
z.number().gte(0) // >= 0(等价于 min)
08
z.number().lt(100) // < 100
09
z.number().lte(100) // <= 100(等价于 max)
10
11
// 正负号
12
z.number().positive() // > 0
13
z.number().negative() // < 0
14
z.number().nonnegative() // >= 0
15
z.number().nonpositive() // <= 0
16
17
// 倍数
18
z.number().multipleOf(5) // 必须是 5 的倍数
19
20
// 特殊值
21
z.number().finite() // 拒绝 Infinity
22
z.number().safe() // 在 Number.MAX_SAFE_INTEGER 范围内

3.1 它不吃字符串

这是新手最常踩的一个坑:

index.ts
1
z.number().parse('42')
2
// 💥 ZodError: Expected number, received string

HTTP 请求里的数字经常以字符串形式传过来(尤其是 query 参数、表单字段)。你如果期望 Zod 能自动把 "42" 转成 42,它不会。Zod 默认是严格模式,不会做隐式类型转换。

要让它做转换,用 z.coerce.number()(本篇第 6 节会讲)。

3.2 NaN 默认是被拒绝的

index.ts
1
z.number().parse(NaN)
2
// 💥 ZodError: Expected number, received nan

虽然 typeof NaN === 'number' 在 JS 里是成立的,但 Zod 觉得这玩意不该被当成合法的 number 放行。这是个好默认,记住就行。

如果你确实要接受 NaN(极少见),用 z.number().or(z.nan()) 这样显式声明。

3.3 小数和 AI 参数

AI 项目里会大量遇到小数参数:temperaturetop_pfrequency_penalty。典型写法:

index.ts
1
const TemperatureSchema = z.number().min(0).max(2)
2
const TopPSchema = z.number().min(0).max(1)

记得 int() 不要乱加——小数加了 int() 直接过不了。

4. z.boolean() 和 z.date()

这两个比较简单,放到一起讲,但都有一个必须知道的「反直觉点」。

4.1 z.boolean() 不吃「类真值」

index.ts
1
z.boolean().parse(true) // ✅ true
2
z.boolean().parse(false) // ✅ false
3
z.boolean().parse(1) // 💥 拒绝
4
z.boolean().parse('true') // 💥 拒绝
5
z.boolean().parse('') // 💥 拒绝

很多 JS 老手下意识会觉得 1 / 'true' / 'yes' 都算 boolean,但 Zod 不买账。这是设计决策:隐式转换会让数据边界变糊,Zod 宁可让你显式写出来。

需要从字符串或数字转 boolean 的时候,请用 z.coerce.boolean(),但注意它有个很坑的行为,第 6 节会讲。

4.2 z.date() 只接受 Date 对象

这个坑踩过的人才会记得:

index.ts
1
z.date().parse(new Date()) // ✅
2
z.date().parse('2024-01-01') // 💥 拒绝
3
z.date().parse(1700000000000) // 💥 拒绝

HTTP 请求里几乎不会有 Date 对象——要么是字符串,要么是时间戳。所以 z.date() 在实际的后端校验中用得比你想象的少,常见用法是:

  • 输入来源是字符串 → 用 z.string().datetime() 做格式校验,保留字符串
  • 确实要得到 Date 对象 → 用 z.coerce.date()(第 6 节)
  • 代码内部的数据模型(已经是 Date 对象) → 用 z.date()

4.3 z.date() 的范围校验

index.ts
1
const FutureDate = z.date().min(new Date()) // 必须是将来
2
const LastYearOnly = z.date()
3
.min(new Date('2024-01-01'))
4
.max(new Date('2024-12-31'))

5. 所有基础类型共享的方法

Zod 里有一组方法是所有 schema 都能用的,不只是基础类型。你会在整个学习过程中反复见到它们:

方法含义示例
.optional()允许 undefinedz.string().optional()
.nullable()允许 nullz.string().nullable()
.nullish()允许 nullundefinedz.string().nullish()
.default(v)undefined 提供默认值z.string().default('anon')
.describe(s)加描述(给文档/LLM 用)z.string().describe('user name')
.refine(fn)自定义校验z.string().refine(s => s.length > 3)
.transform(fn)自定义变换z.string().transform(s => s.trim())

这一组方法后面都会有专题。这里你只要知道它们是通用的,不需要死记每个基础类型单独支持哪些。

6. z.coerce:接受「脏数据」的开关

前面反复提到:Zod 默认严格,不做隐式转换。但现实里有很多你没办法控制输入的场景:

  • 表单提交(HTML input 天生是字符串)
  • URL query 参数(?page=2 里的 2 是字符串)
  • 环境变量(process.env.PORT 一定是字符串)

这时候就该请出 z.coerce.*

index.ts
1
z.coerce.string() // 先调用 String(input)
2
z.coerce.number() // 先调用 Number(input)
3
z.coerce.boolean() // 先调用 Boolean(input)
4
z.coerce.date() // 先调用 new Date(input)

举几个例子:

index.ts
1
z.coerce.number().parse('42') // ✅ 42
2
z.coerce.number().parse('abc') // 💥 Number('abc') 是 NaN,被拒绝
3
z.coerce.date().parse('2024-01-01') // ✅ Date 对象

6.1 coerce.boolean() 的大坑(必看)

z.coerce.boolean() 的行为是 Boolean(input),这意味着:

index.ts
1
z.coerce.boolean().parse('false') // ⚠️ 返回 true!
2
z.coerce.boolean().parse('0') // ⚠️ 返回 true!
3
z.coerce.boolean().parse('') // ✅ 返回 false

原因是 JS 的 Boolean('false') 就是 true——任何非空字符串都是 truthy。

如果你要把字符串 'true' / 'false' 转成对应 boolean,应该自己写逻辑

index.ts
1
const StrictBool = z.enum(['true', 'false'])
2
.transform(v => v === 'true')
3
4
StrictBool.parse('true') // true
5
StrictBool.parse('false') // false
6
StrictBool.parse('yes') // 💥 拒绝

这个坑在环境变量校验里特别常见,专门拎出来提醒一下。

7. 常见陷阱速查表

把前面所有容易踩的坑收成一张表,方便你之后回头查:

场景错误写法正确做法
校验 "42" 这样的数字字符串z.number()z.coerce.number()
校验 ISO 时间字符串z.date()z.string().datetime()
接受 1 / "yes" 作为 truez.boolean()显式用 z.enum+transform
"false" 字符串转成 falsez.coerce.boolean()自己写 transform
希望允许缺省手动判断 undefined.optional() / .default()
允许 null手动判断.nullable() / .nullish()
希望 NaN 合法z.number()显式 .or(z.nan())
希望数字不超过安全整数z.number().safe()

8. 总结

这一篇把 Zod 的 4 个基础类型和它们周边的方法梳理了一遍。你现在应该能把下面这张图在脑子里画出来:

text
01
基础类型
02
├── z.string() ── .min / .max / .email / .url / .regex / .datetime ...
03
├── z.number() ── .int / .min / .max / .positive / .finite ...
04
├── z.boolean() ── 只接受真 boolean
05
├── z.date() ── 只接受 Date 对象
06
07
所有 schema 共享
08
├── .optional() / .nullable() / .nullish()
09
├── .default() / .describe()
10
├── .refine() / .transform()
11
12
需要隐式转换时
13
├── z.coerce.string / number / boolean / date
14
└── ⚠️ coerce.boolean 有坑,字符串 'false' 会被转成 true

一句话带走:

NOTE

Zod 默认是严格的——不转换、不容忍、不猜测。如果你要「宽松」,必须显式用 coercetransform

下一篇进入 z.object()z.array()——真实项目里,你 80% 的 schema 都是这两个的组合。