Skip to content

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)
  • email
  • symbol
  • undefined
  • null
  • void
  • any
  • unknown
  • never

示例

ts
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) 验证数据是否严格等于某个字面量值 (字符串、数字、布尔值)。

ts
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)

ts
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"); // 抛出 ZodError

nativeEnum

ts
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)

ts
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)

ts
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))。
ts
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: true

z.tuple([schemaA, schemaB, ...]) : 验证数据是否为元组 (具有固定长度和特定顺序元素类型的数组)。

ts
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。

ts
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。

ts
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。

ts
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

ts
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

ts
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)

ts
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 作为有效值。字段可以不存在。
ts
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 作为有效值。
ts
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 值无效。
ts
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 添加一个描述性字符串。这本身不影响验证,但可以用于文档生成或自定义错误信息。
ts
import { z } from 'zod'
const userIdSchema = z.string().uuid().describe('用户的唯一标识符 (UUID v4)')
console.log(userIdSchema.description) // "用户的唯一标识符 (UUID v4)"
.transform(transformFn): 在数据成功通过验证后,对其进行转换。transformFn 接收验证后的数据作为参数,并返回转换后的数据
ts
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 值)
ts
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 允许你添加多个错误,并且可以指定错误的路径和消息
ts
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 进行验证。这对于在验证前转换数据类型或格式非常有用
ts
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 进行进一步验证和转换
ts
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 失败)

错误处理与自定义错误

ts
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);