zod
Zod 是一个以 TypeScript 为首的 schema 声明和验证库。这意味着你首先定义一个数据的“形状”或“蓝图”(即 schema),然后 Zod 可以根据这个 schema 来验证实际数据是否符合预期。它不仅仅是做类型检查,更重要的是它在运行时进行数据验证。
原始类型
- string (min、max、length、url、uuid、cuid、cuid2、ulid、regex、startsWith、endsWith、trim、toLowerCase、toUpperCase、nonempty、datetime、ip)
- number (gt、gte、lt、lte、int、positive、nonnegative、negative、nonpositive、multipleOf、finite、safe)
- bigint
- boolean
- date (min、max)
- symbol
- undefined
- null
- void
- any
- unknown
- never
示例
const USERNAME_SCHEMA = z
.string()
.min(3, { message: '用户名至少需要3个字符' })
.max(20, { message: '用户名不能超过20个字符' })
.regex(/^[a-zA-Z0-9_]+$/, { message: '用户名只能包含字母、数字和下划线' })
const EMAIL_SCHEMA = z.email('无效的邮箱地址')
EMAIL_SCHEMA.safeParse('[email protected]')字面量
z.literal(value) 验证数据是否严格等于某个字面量值 (字符串、数字、布尔值)。
import { z } from 'zod'
const statusSchema = z.literal('success')
const versionSchema = z.literal(1)
const exactBooleanSchema = z.literal(true)
console.log(statusSchema.parse('success')) // "success"
// statusSchema.parse("error"); // 抛出 ZodError
// 通常与 union 结合使用
const httpMethodSchema = z.union([
z.literal('GET'),
z.literal('POST'),
z.literal('PUT'),
z.literal('DELETE'),
])
type HttpMethod = z.infer<typeof httpMethodSchema> // "GET" | "POST" | "PUT" | "DELETE"
console.log(httpMethodSchema.parse('POST'))枚举 (Enums)
import { z } from 'zod'
const UserRoleSchema = z.enum(['ADMIN', 'USER', 'GUEST'])
type UserRole = z.infer<typeof UserRoleSchema> // "ADMIN" | "USER" | "GUEST"
console.log(UserRoleSchema.parse('ADMIN'))
// UserRoleSchema.parse("EDITOR"); // 抛出 ZodErrornativeEnum
import { z } from 'zod'
enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE',
}
const ColorSchema = z.nativeEnum(Color)
type ColorType = z.infer<typeof ColorSchema> // Color (原生枚举类型)
console.log(ColorSchema.parse(Color.Red)) // "RED"
console.log(ColorSchema.parse('GREEN')) // "GREEN"
// ColorSchema.parse("YELLOW"); // 抛出 ZodError
enum NumericEnum {
Zero, // 0
One, // 1
}
const NumericEnumSchema = z.nativeEnum(NumericEnum)
console.log(NumericEnumSchema.parse(0)) // 0
console.log(NumericEnumSchema.parse(NumericEnum.One)) // 1对象 (Objects)
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
username: z.string().min(3),
email: z.email(),
age: z.number().positive().optional(), // age 是可选的
isActive: z.boolean().default(true), // isActive 有默认值 true
address: z
.object({
// 嵌套对象
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/), // 美国邮编格式
})
.optional(),
})
type User = z.infer<typeof UserSchema>方法和修饰符
.shape: 获取定义对象形状的对象。UserSchema.shape.username 会给你 username 的 schema。.keyof(): 创建一个由对象键组成的枚举 schema。UserSchema.keyof() 会是 z.enum(['id', 'username', 'email', 'age', 'isActive', 'address'])。.extend({ newKey: newSchema, ... }): 扩展现有对象 schema,添加新的字段。 [16].merge(otherObjectSchema): 合并两个对象 schema。.pick({ keyToPick: true, ... }): 从现有 schema 中选择一部分键来创建新的对象 schema。.omit({ keyToOmit: true, ... }): 从现有 schema 中排除一部分键来创建新的对象 schema。.partial(): 使对象中的所有字段都变为可选。可以传入参数指定哪些字段变为可选 UserSchema.partial({ username: true })。.deepPartial(): 递归地使对象及其嵌套对象中的所有字段都变为可选。.passthrough(): 默认情况下,Zod 会去除对象中未在 schema 中定义的额外字段。使用 .passthrough() 会保留这些额外字段。.strict(message?): 如果对象包含未在 schema 中定义的额外字段,则抛出错误。.strip(): (默认行为) 去除对象中未在 schema 中定义的额外字段。.catchall(valueSchema): 为对象中所有未明确定义的键指定一个 schema。这对于动态键很有用。
数组 (Arrays)
import { z } from 'zod'
const stringArraySchema = z.array(z.string()) // 字符串数组
const numberArraySchema = z.array(z.number()) // 数字数组
const userSchema = z.object({ name: z.string(), age: z.number() })
const userArraySchema = z.array(userSchema) // User 对象数组
console.log(stringArraySchema.parse(['a', 'b', 'c']))
console.log(
userArraySchema.parse([
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
])
)校验方法:
.min(minLength, message?): 数组最小长度。.max(maxLength, message?): 数组最大长度。.length(fixedLength, message?): 数组固定长度。.nonempty(message?): 确保数组至少有一个元素 (等同于 .min(1))。
import { z } from 'zod'
const nonEmptyTagListSchema = z.array(z.string()).nonempty({ message: '标签列表不能为空' })
const fixedSizeTupleLikeSchema = z.array(z.number()).length(3, { message: '需要恰好3个数字' })
console.log(nonEmptyTagListSchema.safeParse([])) // success: false
console.log(nonEmptyTagListSchema.safeParse(['tag1'])) // success: truez.tuple([schemaA, schemaB, ...]) : 验证数据是否为元组 (具有固定长度和特定顺序元素类型的数组)。
import { z } from 'zod'
const stringNumberBooleanTuple = z.tuple([z.string(), z.number(), z.boolean()])
type MyTuple = z.infer<typeof stringNumberBooleanTuple> // [string, number, boolean]
console.log(stringNumberBooleanTuple.parse(['hello', 123, true]))
// stringNumberBooleanTuple.parse(["hello", 123]); // 抛出 ZodError (长度不足)
// stringNumberBooleanTuple.parse(["hello", "world", true]); // 抛出 ZodError (第二个元素类型错误)
// .rest() 用于元组的剩余元素
const nameAndScoresSchema = z.tuple([z.string()]).rest(z.number())
type NameAndScores = z.infer<typeof nameAndScoresSchema> // [string, ...number[]]
console.log(nameAndScoresSchema.parse(['Alice', 100, 90, 80]))
console.log(nameAndScoresSchema.parse(['Bob'])) // 也有效,rest 部分可以为空z.union([schemaA, schemaB, ...]): 验证数据是否符合联合中至少一个 schema。
z.discriminatedUnion(discriminatorKey, [schemaA, schemaB, ...]): 可辨识联合类型。它基于一个共同的“辨别器”字段来确定应该使用哪个 schema 进行验证。这对于处理具有不同形状但共享某个类型字段的对象非常有用。
交叉类型 (Intersections)
z.intersection(schemaA, schemaB): 验证数据是否同时符合 schemaA 和 schemaB。通常用于合并对象 schema。
import { z } from 'zod';
const HasIdSchema = z.object({ id: z.string() });
const HasNameSchema = z.object({ name: z.string() });
const IdentifiedNameSchema = z.intersection(HasIdSchema, HasNameSchema);
// 或者使用 .and() 语法糖: const IdentifiedNameSchema = HasIdSchema.and(HasNameSchema);
type IdentifiedName = z.infer<typeof IdentifiedNameSchema>; // { id: string; name: string; }
console.log(IdentifiedNameSchema.parse({ id: "123", name: "Resource" }));
// IdentifiedNameSchema.parse({ id: "123" }); // 抛出 ZodError (缺少 name)
``` 注意:对于原始类型的交叉(如 `z.string().and(z.number())`),结果通常是 `z.never()`,因为一个值不能同时是字符串和数字。Record 类型
z.record(keySchema, valueSchema): 验证数据是否为类似字典的对象,其中所有键都符合 keySchema (通常是 z.string() 或 z.number() 或 z.enum()),所有值都符合 valueSchema。
import { z } from 'zod'
// 键是字符串,值是数字
const scoreRecordSchema = z.record(z.string(), z.number())
type ScoreRecord = z.infer<typeof scoreRecordSchema> // { [x: string]: number; }
console.log(scoreRecordSchema.parse({ math: 90, science: 85 }))
// scoreRecordSchema.parse({ math: "A" }); // 抛出 ZodError (值类型错误)
// 使用枚举作为键
const UserRoleEnum = z.enum(['admin', 'editor', 'viewer'])
const PermissionsSchema = z.record(UserRoleEnum, z.boolean())
type Permissions = z.infer<typeof PermissionsSchema>
/*
type Permissions = {
admin: boolean;
editor: boolean;
viewer: boolean;
}
*/
console.log(PermissionsSchema.parse({ admin: true, editor: true, viewer: false }))Map 类型
z.map(keySchema, valueSchema): 验证数据是否为 JavaScript Map 对象,并且其键和值分别符合 keySchema 和 valueSchema。
import { z } from 'zod'
const userMapSchema = z.map(z.string().uuid(), z.object({ name: z.string() }))
type UserMap = z.infer<typeof userMapSchema> // Map<string, { name: string; }>
const myMap = new Map()
myMap.set('a1b2c3d4-e5f6-7890-1234-567890abcdef', { name: 'Alice' })
console.log(userMapSchema.parse(myMap))Set 类型
z.set(valueSchema) : 验证数据是否为 JavaScript Set 对象,并且其所有值都符合 valueSchema
import { z } from 'zod'
const stringSetSchema = z.set(z.string())
type StringSet = z.infer<typeof stringSetSchema> // Set<string>
const mySet = new Set(['apple', 'banana'])
console.log(stringSetSchema.parse(mySet))Promise 类型
z.promise(valueSchema): 验证数据是否为 Promise,并且该 Promise resolve 后的值符合 valueSchema
import { z } from 'zod'
const stringPromiseSchema = z.promise(z.string())
type StringPromise = z.infer<typeof stringPromiseSchema> // Promise<string>
async function fetchData(): Promise<string> {
return 'Data from API'
}
const promise = fetchData()
console.log(stringPromiseSchema.parse(promise)) // 验证 Promise 对象本身
// 通常与 async parse 结合使用来验证解析后的值
stringPromiseSchema.parseAsync(promise).then((data) => {
console.log('Promise resolved data:', data) // data is string
})函数类型 (Experimental)
import { z } from 'zod'
const sumFunctionSchema = z
.function()
.args(z.number(), z.number()) // 定义两个参数,都是数字
.returns(z.number()) // 定义返回值是数字
type SumFunction = z.infer<typeof sumFunctionSchema>
// (arg0: number, arg1: number) => number
// 使用 .implement 创建一个经过校验的函数
const safeSum = sumFunctionSchema.implement((a, b) => {
return a + b
})
console.log(safeSum(5, 10)) // 15
try {
// safeSum("5", 10); // 会在参数校验时抛出 ZodError
// safeSum(5, "10"); // 会在参数校验时抛出 ZodError
} catch (e) {
console.error(e.errors)
}
// 示例:如果函数实现返回了错误类型
const faultySum = z
.function()
.args(z.number(), z.number())
.returns(z.number()) // 期望数字
.implement((a, b) => {
// @ts-ignore 为了演示错误返回类型
return `${a + b}` // 实际返回字符串
})
try {
// faultySum(1,2); // 会在返回值校验时抛出 ZodError
} catch (e) {
console.error('Faulty sum error:', e.errors)
}Schema 修饰符与工具
这些方法可以链式调用到大多数 Zod schema 上,以添加额外的行为或约束。
.optional(): 使 schema 接受 undefined 作为有效值。字段可以不存在。
import { z } from 'zod'
const optionalStringSchema = z.string().optional()
type OptionalString = z.infer<typeof optionalStringSchema> // string | undefined
console.log(optionalStringSchema.parse('hello'))
console.log(optionalStringSchema.parse(undefined))
// optionalStringSchema.parse(null); // 抛出 ZodError.nullable(): 使 schema 接受 null 作为有效值。
import { z } from 'zod'
const nullableStringSchema = z.string().nullable()
type NullableString = z.infer<typeof nullableStringSchema> // string | null
console.log(nullableStringSchema.parse('hello'))
console.log(nullableStringSchema.parse(null))
// nullableStringSchema.parse(undefined); // 抛出 ZodError
/* 你可以组合使用 .optional().nullable() (或 .nullable().optional()) 来表示一个值可以是 T | null | undefined。更简洁的方式是使用 .nullish(),它等同于 .optional().nullable()。*/
const nullishStringSchema = z.string().nullish() // string | null | undefined
type NullishString = z.infer<typeof nullishStringSchema>.default(value): 如果输入数据为 undefined,则提供一个默认值。注意:它只对 undefined 生效,对 null 或其他 falsy 值无效。
import { z } from 'zod'
const nameSchema = z.string().default('Anonymous')
type Name = z.infer<typeof nameSchema> // string
console.log(nameSchema.parse('Alice')) // "Alice"
console.log(nameSchema.parse(undefined)) // "Anonymous" (默认值生效)
// console.log(nameSchema.parse(null)); // 抛出 ZodError,因为 null 不是 string 也不是 undefined
/* 如果希望 null 也触发默认值,可以使用 .preprocess(): */
const nameOrDefaultSchema = z.preprocess(
(val) => (val === null ? undefined : val),
z.string().default('Anonymous')
)
console.log(nameOrDefaultSchema.parse(null)) // "Anonymous".describe(description): 为 schema 添加一个描述性字符串。这本身不影响验证,但可以用于文档生成或自定义错误信息。
import { z } from 'zod'
const userIdSchema = z.string().uuid().describe('用户的唯一标识符 (UUID v4)')
console.log(userIdSchema.description) // "用户的唯一标识符 (UUID v4)".transform(transformFn): 在数据成功通过验证后,对其进行转换。transformFn 接收验证后的数据作为参数,并返回转换后的数据
import { z } from 'zod'
const stringToNumberSchema = z.string().regex(/^\d+$/, '必须是数字字符串').transform(Number) // 将验证后的字符串转换为数字
type StringToNumber = z.infer<typeof stringToNumberSchema> // number
console.log(stringToNumberSchema.parse('123')) // 123 (数字类型)
// stringToNumberSchema.parse("abc"); // 验证失败 (regex)
const userFullNameSchema = z
.object({
firstName: z.string(),
lastName: z.string(),
})
.transform((user) => `${user.firstName} ${user.lastName}`)
type UserFullName = z.infer<typeof userFullNameSchema> // string
console.log(userFullNameSchema.parse({ firstName: 'Jane', lastName: 'Doe' })) // "Jane Doe".refine(validatorFn, messageOrParams): 添加自定义验证逻辑。validatorFn 接收要验证的数据,如果数据有效则返回 true (或一个 truthy 值),无效则返回 false (或一个 falsy 值)
import { z } from 'zod'
const passwordSchema = z
.string()
.min(8, '密码至少需要8个字符')
.refine((s) => /[A-Z]/.test(s), { message: '密码必须包含至少一个大写字母' })
.refine((s) => /[a-z]/.test(s), { message: '密码必须包含至少一个小写字母' })
.refine((s) => /\d/.test(s), { message: '密码必须包含至少一个数字' })
console.log(passwordSchema.safeParse('Password123')) // success: true
console.log(passwordSchema.safeParse('password')) // success: false, 错误信息指向大写字母和数字的 refine
// refine 可以用于对象级别,例如比较两个字段
const registrationSchema = z
.object({
password: z.string().min(8),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: '两次输入的密码不匹配',
path: ['confirmPassword'], // 指定错误关联到 confirmPassword 字段
})
console.log(registrationSchema.safeParse({ password: 'test1234', confirmPassword: 'test1234' })) // success: true
const result = registrationSchema.safeParse({ password: 'test1234', confirmPassword: 'test4321' })
if (!result.success) {
console.log(result.error.flatten().fieldErrors) // { confirmPassword: [ '两次输入的密码不匹配' ] }
}.superRefine(superValidatorFn): 一个更强大的 refine 版本,它接收一个额外的 ctx (ZodRefinementCtx) 参数。ctx 允许你添加多个错误,并且可以指定错误的路径和消息
import { z } from 'zod'
const complexObjectSchema = z
.object({
startDate: z.date(),
endDate: z.date(),
value: z.number(),
})
.superRefine((data, ctx) => {
if (data.endDate < data.startDate) {
ctx.addIssue({
code: z.ZodIssueCode.custom, // 自定义错误码
message: '结束日期不能早于开始日期',
path: ['endDate'], // 错误关联到 endDate 字段
})
}
if (data.startDate > new Date() && data.value < 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: '对于未来的日期,值不能为负',
path: ['value'],
})
}
// 如果有需要,可以添加更多 issue
})
const testData1 = { startDate: new Date('2024-01-15'), endDate: new Date('2024-01-10'), value: 100 }
const result1 = complexObjectSchema.safeParse(testData1)
if (!result1.success) {
console.log('Validation 1 errors:', result1.error.flatten())
/*
Validation 1 errors: {
fieldErrors: { endDate: [ '结束日期不能早于开始日期' ] },
formErrors: []
}
*/
}
const futureDate = new Date()
futureDate.setDate(futureDate.getDate() + 5) // 5天后的日期
const testData2 = {
startDate: futureDate,
endDate: new Date(futureDate.getTime() + 86400000),
value: -10,
}
const result2 = complexObjectSchema.safeParse(testData2)
if (!result2.success) {
console.log('Validation 2 errors:', result2.error.flatten())
/*
Validation 2 errors: {
fieldErrors: { value: [ '对于未来的日期,值不能为负' ] },
formErrors: []
}
*/
}.preprocess(preprocessFn, schema): 在数据传递给内部 schema 进行验证之前,对其进行预处理。preprocessFn 接收原始输入,其返回值将被传递给 schema 进行验证。这对于在验证前转换数据类型或格式非常有用
import { z } from 'zod'
// 示例:将输入的字符串(如果是数字字符串)或数字统一转换为数字
const flexibleNumberSchema = z.preprocess(
(arg) => {
if (typeof arg === 'string') {
const num = parseFloat(arg)
return isNaN(num) ? arg : num // 如果不能转为数字,则返回原字符串让后续的 z.number() 报错
}
return arg
},
z.number({ invalid_type_error: '必须是数字或数字字符串' })
)
console.log(flexibleNumberSchema.parse('123.45')) // 123.45 (数字)
console.log(flexibleNumberSchema.parse(500)) // 500 (数字)
// console.log(flexibleNumberSchema.safeParse("abc")); // success: false, error...
// 示例:处理可能为 "true"/"false" 字符串的布尔值
const booleanLikeSchema = z.preprocess((val) => {
if (typeof val === 'string') {
if (val.toLowerCase() === 'true') return true
if (val.toLowerCase() === 'false') return false
}
return val
}, z.boolean())
console.log(booleanLikeSchema.parse('true')) // true
console.log(booleanLikeSchema.parse('FALSE')) // false
console.log(booleanLikeSchema.parse(true)) // true
// console.log(booleanLikeSchema.safeParse("not a boolean")); // success: false.pipe(outputSchema): 链式验证。首先用当前 schema 验证数据,如果成功,则将结果(可能是经过转换的)传递给 outputSchema 进行进一步验证和转换
import { z } from 'zod'
// 步骤1: 确保输入是字符串,并转换为数字
const stringToNumber = z.string().transform((val) => parseInt(val, 10))
// 步骤2: 确保转换后的数字是正数
const positiveNumber = z.number().positive()
// 使用 .pipe() 连接它们
const stringToPositiveNumberSchema = stringToNumber.pipe(positiveNumber)
// 等价于: z.string().transform(val => parseInt(val, 10)).pipe(z.number().positive())
// 但更清晰的写法是分开定义
console.log(stringToPositiveNumberSchema.parse('123')) // 123
// stringToPositiveNumberSchema.parse("0"); // 抛出 ZodError (positiveNumber 校验失败)
// stringToPositiveNumberSchema.parse("-10"); // 抛出 ZodError (positiveNumber 校验失败)
// stringToPositiveNumberSchema.parse("abc"); // 抛出 ZodError (stringToNumber 校验失败,因为 parseInt("abc") 是 NaN)
// 注意:如果 stringToNumber 内部的 transform 返回了 NaN,
// 那么 NaN 会传递给 positiveNumber,positiveNumber 会校验失败。
// 如果希望在 parseInt 失败时就给出明确错误,可以这样做:
const robustStringToPositiveNumberSchema = z
.string()
.regex(/^\d+$/, '必须是纯数字字符串') // 先确保是数字字符串
.transform((val) => parseInt(val, 10))
.pipe(z.number().positive('数字必须为正数'))
console.log(robustStringToPositiveNumberSchema.safeParse('100')) // success: true, data: 100
console.log(robustStringToPositiveNumberSchema.safeParse('-5')) // success: false, (regex 失败)
console.log(robustStringToPositiveNumberSchema.safeParse('text')) // success: false, (regex 失败)错误处理与自定义错误
import { z, ZodErrorMap, ZodIssueCode } from 'zod';
// 自定义中文错误映射
const customErrorMap: ZodErrorMap = (issue, ctx) => {
let message: string;
switch (issue.code) {
case ZodIssueCode.invalid_type:
if (issue.expected === "string") {
message = `我们期望一个文本,但收到了 ${issue.received}`;
} else if (issue.expected === "number") {
message = `我们期望一个数字,但收到了 ${issue.received}`;
} else {
message = `类型无效,期望 ${issue.expected},但收到了 ${issue.received}`;
}
break;
case ZodIssueCode.too_small:
if (issue.type === "string") {
message = `内容太短了,至少需要 ${issue.minimum} 个字符`;
} else if (issue.type === "array") {
message = `列表项太少了,至少需要 ${issue.minimum} 项`;
} else if (issue.type === "number") {
message = `数字太小了,必须大于${issue.inclusive ? '=' : ''} ${issue.minimum}`;
} else {
message = "内容太小了";
}
break;
case ZodIssueCode.too_big:
// ... 类似处理
message = "内容太大了";
break;
case ZodIssueCode.custom:
message = issue.message || "自定义校验失败"; // 使用 issue 中提供的 message
break;
default:
message = ctx.defaultError; // 对于未处理的 code,使用 Zod 的默认消息
}
return { message };
};
// 设置全局错误映射 (在你的应用入口处执行一次)
// z.setErrorMap(customErrorMap);
// 或者在 parse/safeParse 时局部使用
const mySchema = z.string().min(5);
const data = 123;
// 使用局部错误映射
const result = mySchema.safeParse(data, { errorMap: customErrorMap });
if (!result.success) {
console.log("自定义错误信息:");
result.error.issues.forEach(issue => console.log(` ${issue.message}`));
/*
自定义错误信息:
我们期望一个文本,但收到了 number
*/
}
// 如果不设置全局,再次使用默认错误映射
const mySchema2 = z.number().gte(10);
const result2 = mySchema2.safeParse(5, { errorMap: customErrorMap });
if (!result2.success) {
console.log("自定义数字错误信息:");
result2.error.issues.forEach(issue => console.log(` ${issue.message}`));
/*
自定义数字错误信息:
数字太小了,必须大于= 10
*/
}
```这对于国际化 (i18n) 错误消息或统一应用内的错误提示风格非常有用。
## 6. 类型推断 (Type Inference)
Zod 最强大的特性之一是能够从 schema 自动推断出 TypeScript 类型。这是通过 `z.infer<typeof yourSchema>` 实现的。 [[3]](https://www.showapi.com/news/article/67eb4f504ddd79013c000fc6)[[10]](https://cloud.tencent.com/developer/article/2514901)
```typescript
import { z } from 'zod';
const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string(),
price: z.number().positive(),
tags: z.array(z.string()).optional(),
inventory: z.object({
quantity: z.number().int().nonnegative(),
inStock: z.boolean(),
}).default({ quantity: 0, inStock: false }),
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
});
// 使用 z.infer 推断类型
type Product = z.infer<typeof ProductSchema>;
/*
推断出的 Product 类型如下:
type Product = {
id: string;
name: string;
price: number;
tags?: string[] | undefined;
inventory: { // 注意这里因为有 default,所以 inventory 本身不是可选的
quantity: number;
inStock: boolean;
};
attributes?: Record<string, string | number | boolean> | undefined;
}
*/
// 现在可以在代码中使用 Product 类型,享受类型安全
function displayProduct(product: Product) {
console.log(`产品名称: ${product.name}`);
console.log(`价格: ¥${product.price.toFixed(2)}`);
if (product.tags && product.tags.length > 0) {
console.log(`标签: ${product.tags.join(', ')}`);
}
console.log(`库存: ${product.inventory.quantity}件, ${product.inventory.inStock ? '有货' : '无货'}`);
}
const validProductData = {
id: "123e4567-e89b-12d3-a456-426614174000",
name: "超级笔记本",
price: 7999.99,
tags: ["高性能", "轻薄"],
// inventory 会使用默认值
};
const parsedProduct = ProductSchema.parse(validProductData); // parsedProduct 的类型是 Product
displayProduct(parsedProduct);
const anotherProduct: Product = {
id: "another-uuid",
name: "智能手表",
price: 1299,
// tags 是可选的,可以不提供
inventory: { // inventory 不是可选的,必须提供,或者让 Zod 使用默认值
quantity: 50,
inStock: true,
},
attributes: {
color: "Black",
waterproof_level: 5
}
};
displayProduct(anotherProduct);