valibot-usage
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseValibot Usage
Valibot 使用指南
This skill helps you work effectively with Valibot, the modular and type-safe schema library for validating structural data.
本内容将帮助你高效使用Valibot——一款用于验证结构化数据的模块化、类型安全的Schema库。
When to use this skill
适用场景
- When the user asks about schema validation with Valibot
- When creating or modifying Valibot schemas
- When parsing or validating user input
- When the user mentions Valibot, schema, or validation
- When migrating from Zod to Valibot
- 当用户询问关于Valibot的Schema验证相关问题时
- 当创建或修改Valibot Schema时
- 当解析或验证用户输入时
- 当用户提及Valibot、Schema或验证时
- 当从Zod迁移到Valibot时
CRITICAL: Valibot vs Zod — Do Not Confuse!
⚠️ 重要提示:Valibot与Zod的区别——请勿混淆!
Valibot and Zod have different APIs. Never mix them up!
Valibot和Zod的API完全不同,绝不能混用!
Key Differences
核心差异
| Feature | Zod ❌ | Valibot ✅ |
|---|---|---|
| Import | | |
| Validations | Chained methods: | Pipeline: |
| Parsing | | |
| Safe parsing | | |
| Optional | | |
| Nullable | | |
| Default | | |
| Transform | | |
| Refine/Check | | |
| Enum | | |
| Native enum | | |
| Union | | |
| Discriminated union | | |
| Intersection | | |
| Min/max length | | |
| Min/max value | | |
| Infer type | | |
| Infer input | | |
| 特性 | Zod 错误写法 ❌ | Valibot 正确写法 ✅ |
|---|---|---|
| 导入方式 | | |
| 链式验证写法 | | 管道式: |
| 解析数据 | | |
| 安全解析 | | |
| 可选字段 | | |
| 可空字段 | | |
| 默认值 | | |
| 类型转换 | | |
| 自定义验证 | | |
| 枚举值 | | |
| 原生枚举 | | |
| 联合类型 | | |
| 区分联合类型 | | |
| 交叉类型 | | |
| 长度限制 | | |
| 数值范围 | | |
| 推断输出类型 | | |
| 推断输入类型 | | |
Common Mistakes to Avoid
需避免的常见错误
typescript
// ❌ WRONG - This is Zod syntax, NOT Valibot!
const Schema = v.string().email().min(5);
const result = Schema.parse(data);
// ✅ CORRECT - Valibot uses functions and pipelines
const Schema = v.pipe(v.string(), v.email(), v.minLength(5));
const result = v.parse(Schema, data);typescript
// ❌ WRONG - Zod-style optional
const Schema = v.object({
name: v.string().optional(),
});
// ✅ CORRECT - Valibot wraps with optional()
const Schema = v.object({
name: v.optional(v.string()),
});typescript
// ❌ WRONG - Zod-style default
const Schema = v.string().default("hello");
// ✅ CORRECT - Valibot uses second argument
const Schema = v.optional(v.string(), "hello");typescript
// ❌ 错误写法——这是Zod语法,不是Valibot的!
const Schema = v.string().email().min(5);
const result = Schema.parse(data);
// ✅ 正确写法——Valibot使用函数和管道式写法
const Schema = v.pipe(v.string(), v.email(), v.minLength(5));
const result = v.parse(Schema, data);typescript
// ❌ 错误写法——Zod风格的可选字段
const Schema = v.object({
name: v.string().optional(),
});
// ✅ 正确写法——Valibot用optional()包裹
const Schema = v.object({
name: v.optional(v.string()),
});typescript
// ❌ 错误写法——Zod风格的默认值
const Schema = v.string().default("hello");
// ✅ 正确写法——Valibot使用第二个参数设置默认值
const Schema = v.optional(v.string(), "hello");Installation
安装
bash
npm install valibot # npm
yarn add valibot # yarn
pnpm add valibot # pnpm
bun add valibot # bunImport with a wildcard (recommended):
typescript
import * as v from "valibot";Or with individual imports:
typescript
import { object, string, pipe, email, parse } from "valibot";bash
npm install valibot # npm
yarn add valibot # yarn
pnpm add valibot # pnpm
bun add valibot # bun推荐使用通配符导入:
typescript
import * as v from "valibot";或者使用单独导入:
typescript
import { object, string, pipe, email, parse } from "valibot";Mental Model
核心概念
Valibot's API is divided into three main concepts:
Valibot的API分为三个核心部分:
1. Schemas
1. Schema定义
Schemas define the expected data type. They are the starting point.
typescript
import * as v from "valibot";
// Primitive schemas
const StringSchema = v.string();
const NumberSchema = v.number();
const BooleanSchema = v.boolean();
const DateSchema = v.date();
// Complex schemas
const ArraySchema = v.array(v.string());
const ObjectSchema = v.object({
name: v.string(),
age: v.number(),
});Schema用于定义预期的数据类型,是所有操作的起点。
typescript
import * as v from "valibot";
// 基础类型Schema
const StringSchema = v.string();
const NumberSchema = v.number();
const BooleanSchema = v.boolean();
const DateSchema = v.date();
// 复杂类型Schema
const ArraySchema = v.array(v.string());
const ObjectSchema = v.object({
name: v.string(),
age: v.number(),
});2. Methods
2. 操作方法
Methods help you use or modify schemas. The schema is always the first argument.
typescript
// Parsing
const result = v.parse(StringSchema, "hello");
const safeResult = v.safeParse(StringSchema, "hello");
// Type guard
if (v.is(StringSchema, data)) {
// data is typed as string
}操作方法用于使用或修改Schema,Schema始终作为第一个参数。
typescript
// 解析数据
const result = v.parse(StringSchema, "hello");
const safeResult = v.safeParse(StringSchema, "hello");
// 类型守卫
if (v.is(StringSchema, data)) {
// 此处data会被推断为string类型
}3. Actions
3. 验证/转换动作
Actions validate or transform data within a . They MUST be used inside pipelines.
pipe()typescript
// Actions are used in pipe()
const EmailSchema = v.pipe(
v.string(),
v.trim(),
v.email(),
v.endsWith("@example.com"),
);动作用于在中验证或转换数据,必须在管道中使用。
pipe()typescript
// 动作需在pipe()中使用
const EmailSchema = v.pipe(
v.string(),
v.trim(),
v.email(),
v.endsWith("@example.com"),
);Pipelines
管道式验证
Pipelines extend schemas with validation and transformation actions. A pipeline always starts with a schema, followed by actions.
typescript
import * as v from "valibot";
const UsernameSchema = v.pipe(
v.string(),
v.trim(),
v.minLength(3, "Username must be at least 3 characters"),
v.maxLength(20, "Username must be at most 20 characters"),
v.regex(
/^[a-z0-9_]+$/i,
"Username can only contain letters, numbers, and underscores",
),
);
const AgeSchema = v.pipe(
v.number(),
v.integer("Age must be a whole number"),
v.minValue(0, "Age cannot be negative"),
v.maxValue(150, "Age cannot exceed 150"),
);管道式验证通过验证和转换动作扩展Schema,管道始终以Schema开头,后续跟多个动作。
typescript
import * as v from "valibot";
const UsernameSchema = v.pipe(
v.string(),
v.trim(),
v.minLength(3, "用户名长度至少为3个字符"),
v.maxLength(20, "用户名长度不能超过20个字符"),
v.regex(
/^[a-z0-9_]+$/i,
"用户名只能包含字母、数字和下划线",
),
);
const AgeSchema = v.pipe(
v.number(),
v.integer("年龄必须是整数"),
v.minValue(0, "年龄不能为负数"),
v.maxValue(150, "年龄不能超过150岁"),
);Common Validation Actions
常见验证动作
String validations:
- — Valid email format
v.email() - — Valid URL format
v.url() - — Valid UUID format
v.uuid() - — Match regex pattern
v.regex(pattern) - — Minimum length
v.minLength(n) - — Maximum length
v.maxLength(n) - — Exact length
v.length(n) - — Not empty string
v.nonEmpty() - — Starts with string
v.startsWith(str) - — Ends with string
v.endsWith(str) - — Contains string
v.includes(str)
Number validations:
- — Minimum value (>=)
v.minValue(n) - — Maximum value (<=)
v.maxValue(n) - — Greater than (>)
v.gtValue(n) - — Less than (<)
v.ltValue(n) - — Must be integer
v.integer() - — Must be finite
v.finite() - — Safe integer range
v.safeInteger() - — Must be multiple of n
v.multipleOf(n)
Array validations:
- — Minimum items
v.minLength(n) - — Maximum items
v.maxLength(n) - — Exact item count
v.length(n) - — At least one item
v.nonEmpty() - — Contains item
v.includes(item) - — Does not contain item
v.excludes(item)
字符串验证:
- — 验证邮箱格式
v.email() - — 验证URL格式
v.url() - — 验证UUID格式
v.uuid() - — 匹配正则表达式
v.regex(pattern) - — 最小长度
v.minLength(n) - — 最大长度
v.maxLength(n) - — 精确长度
v.length(n) - — 非空字符串
v.nonEmpty() - — 以指定字符串开头
v.startsWith(str) - — 以指定字符串结尾
v.endsWith(str) - — 包含指定字符串
v.includes(str)
数值验证:
- — 最小值(>=)
v.minValue(n) - — 最大值(<=)
v.maxValue(n) - — 大于指定值(>)
v.gtValue(n) - — 小于指定值(<)
v.ltValue(n) - — 必须是整数
v.integer() - — 必须是有限数
v.finite() - — 安全整数范围
v.safeInteger() - — 必须是n的倍数
v.multipleOf(n)
数组验证:
- — 最小元素数量
v.minLength(n) - — 最大元素数量
v.maxLength(n) - — 精确元素数量
v.length(n) - — 至少包含一个元素
v.nonEmpty() - — 包含指定元素
v.includes(item) - — 不包含指定元素
v.excludes(item)
Custom Validation with check()
使用check()自定义验证
typescript
const PasswordSchema = v.pipe(
v.string(),
v.minLength(8),
v.check(
(input) => /[A-Z]/.test(input),
"Password must contain an uppercase letter",
),
v.check((input) => /[0-9]/.test(input), "Password must contain a number"),
);typescript
const PasswordSchema = v.pipe(
v.string(),
v.minLength(8),
v.check(
(input) => /[A-Z]/.test(input),
"密码必须包含大写字母",
),
v.check((input) => /[0-9]/.test(input), "密码必须包含数字"),
);Value Transformations
值转换
These actions modify the value without changing its type:
String transformations:
- — Remove leading/trailing whitespace
v.trim() - — Remove leading whitespace
v.trimStart() - — Remove trailing whitespace
v.trimEnd() - — Convert to lowercase
v.toLowerCase() - — Convert to uppercase
v.toUpperCase()
Number transformations:
- — Clamp to minimum value (if less than n, set to n)
v.toMinValue(n) - — Clamp to maximum value (if greater than n, set to n)
v.toMaxValue(n)
typescript
const NormalizedEmailSchema = v.pipe(
v.string(),
v.trim(),
v.toLowerCase(),
v.email(),
);
// Clamp number to range 0-100
const PercentageSchema = v.pipe(v.number(), v.toMinValue(0), v.toMaxValue(100));以下动作可修改值但不改变其类型:
字符串转换:
- — 去除首尾空格
v.trim() - — 去除开头空格
v.trimStart() - — 去除结尾空格
v.trimEnd() - — 转换为小写
v.toLowerCase() - — 转换为大写
v.toUpperCase()
数值转换:
- — 限制最小值(小于n则设为n)
v.toMinValue(n) - — 限制最大值(大于n则设为n)
v.toMaxValue(n)
typescript
const NormalizedEmailSchema = v.pipe(
v.string(),
v.trim(),
v.toLowerCase(),
v.email(),
);
// 将数值限制在0-100范围内
const PercentageSchema = v.pipe(v.number(), v.toMinValue(0), v.toMaxValue(100));Type Transformations
类型转换
For converting between data types, use these built-in transformation actions:
- — Convert to number
v.toNumber() - — Convert to string
v.toString() - — Convert to boolean
v.toBoolean() - — Convert to bigint
v.toBigint() - — Convert to Date
v.toDate()
typescript
// Convert string to number
const PortSchema = v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(1));
// Convert ISO string to Date
const TimestampSchema = v.pipe(v.string(), v.isoDateTime(), v.toDate());
// Convert to boolean
const FlagSchema = v.pipe(v.string(), v.toBoolean());如需在数据类型间转换,使用以下内置转换动作:
- — 转换为数值
v.toNumber() - — 转换为字符串
v.toString() - — 转换为布尔值
v.toBoolean() - — 转换为大整数
v.toBigint() - — 转换为Date对象
v.toDate()
typescript
// 将字符串转换为数值
const PortSchema = v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(1));
// 将ISO字符串转换为Date对象
const TimestampSchema = v.pipe(v.string(), v.isoDateTime(), v.toDate());
// 转换为布尔值
const FlagSchema = v.pipe(v.string(), v.toBoolean());Custom Transformations
使用transform()自定义转换
For custom transformations, use :
v.transform()typescript
const DateStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// Custom object transformation
const UserSchema = v.pipe(
v.object({
firstName: v.string(),
lastName: v.string(),
}),
v.transform((input) => ({
...input,
fullName: `${input.firstName} ${input.lastName}`,
})),
);typescript
const DateStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// 自定义对象转换
const UserSchema = v.pipe(
v.object({
firstName: v.string(),
lastName: v.string(),
}),
v.transform((input) => ({
...input,
fullName: `${input.firstName} ${input.lastName}`,
})),
);Object Schemas
对象Schema
Basic Object
基础对象Schema
typescript
const UserSchema = v.object({
id: v.number(),
name: v.string(),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.number()),
});
type User = v.InferOutput<typeof UserSchema>;typescript
const UserSchema = v.object({
id: v.number(),
name: v.string(),
email: v.pipe(v.string(), v.email()),
age: v.optional(v.number()),
});
type User = v.InferOutput<typeof UserSchema>;Object Variants
对象Schema的变体
typescript
// Regular object - strips unknown keys (default)
const ObjectSchema = v.object({ key: v.string() });
// Loose object - allows and preserves unknown keys
const LooseObjectSchema = v.looseObject({ key: v.string() });
// Strict object - throws on unknown keys
const StrictObjectSchema = v.strictObject({ key: v.string() });
// Object with rest - validates unknown keys against a schema
const ObjectWithRestSchema = v.objectWithRest(
{ key: v.string() },
v.number(), // unknown keys must be numbers
);typescript
// 常规对象 - 自动剔除未知字段(默认行为)
const ObjectSchema = v.object({ key: v.string() });
// 宽松对象 - 允许并保留未知字段
const LooseObjectSchema = v.looseObject({ key: v.string() });
// 严格对象 - 存在未知字段时抛出错误
const StrictObjectSchema = v.strictObject({ key: v.string() });
// 带剩余字段的对象 - 未知字段需符合指定Schema
const ObjectWithRestSchema = v.objectWithRest(
{ key: v.string() },
v.number(), // 未知字段必须是数值类型
);Optional and Nullable Fields
可选和可空字段
typescript
const ProfileSchema = v.object({
// Required
name: v.string(),
// Optional (can be undefined or missing)
nickname: v.optional(v.string()),
// Optional with default
role: v.optional(v.string(), "user"),
// Nullable (can be null)
avatar: v.nullable(v.string()),
// Nullish (can be null or undefined)
bio: v.nullish(v.string()),
// Nullish with default
theme: v.nullish(v.string(), "light"),
});typescript
const ProfileSchema = v.object({
// 必填字段
name: v.string(),
// 可选字段(可为undefined或不存在)
nickname: v.optional(v.string()),
// 带默认值的可选字段
role: v.optional(v.string(), "user"),
// 可空字段(可为null)
avatar: v.nullable(v.string()),
// 可为null或undefined的字段
bio: v.nullish(v.string()),
// 带默认值的nullish字段
theme: v.nullish(v.string(), "light"),
});Object Methods
对象Schema的操作方法
typescript
const BaseSchema = v.object({
id: v.number(),
name: v.string(),
email: v.string(),
password: v.string(),
});
// Pick specific keys
const PublicUserSchema = v.pick(BaseSchema, ["id", "name"]);
// Omit specific keys
const UserWithoutPasswordSchema = v.omit(BaseSchema, ["password"]);
// Make all optional
const PartialUserSchema = v.partial(BaseSchema);
// Make all required
const RequiredUserSchema = v.required(PartialUserSchema);
// Merge objects
const ExtendedUserSchema = v.object({
...BaseSchema.entries,
createdAt: v.date(),
});typescript
const BaseSchema = v.object({
id: v.number(),
name: v.string(),
email: v.string(),
password: v.string(),
});
// 选取指定字段
const PublicUserSchema = v.pick(BaseSchema, ["id", "name"]);
// 剔除指定字段
const UserWithoutPasswordSchema = v.omit(BaseSchema, ["password"]);
// 将所有字段设为可选
const PartialUserSchema = v.partial(BaseSchema);
// 将所有字段设为必填
const RequiredUserSchema = v.required(PartialUserSchema);
// 合并对象Schema
const ExtendedUserSchema = v.object({
...BaseSchema.entries,
createdAt: v.date(),
});Cross-Field Validation
跨字段验证
typescript
const RegistrationSchema = v.pipe(
v.object({
password: v.pipe(v.string(), v.minLength(8)),
confirmPassword: v.string(),
}),
v.forward(
v.partialCheck(
[["password"], ["confirmPassword"]],
(input) => input.password === input.confirmPassword,
"Passwords do not match",
),
["confirmPassword"],
),
);typescript
const RegistrationSchema = v.pipe(
v.object({
password: v.pipe(v.string(), v.minLength(8)),
confirmPassword: v.string(),
}),
v.forward(
v.partialCheck(
[["password"], ["confirmPassword"]],
(input) => input.password === input.confirmPassword,
"两次输入的密码不一致",
),
["confirmPassword"],
),
);Arrays and Tuples
数组和元组
Arrays
数组Schema
typescript
const TagsSchema = v.pipe(
v.array(v.string()),
v.minLength(1, "At least one tag required"),
v.maxLength(10, "Maximum 10 tags allowed"),
);
// Array of objects
const UsersSchema = v.array(
v.object({
id: v.number(),
name: v.string(),
}),
);typescript
const TagsSchema = v.pipe(
v.array(v.string()),
v.minLength(1, "至少需要一个标签"),
v.maxLength(10, "最多允许10个标签"),
);
// 对象数组Schema
const UsersSchema = v.array(
v.object({
id: v.number(),
name: v.string(),
}),
);Tuples
元组Schema
typescript
// Fixed-length array with specific types
const CoordinatesSchema = v.tuple([v.number(), v.number()]);
// Type: [number, number]
// Tuple with rest
const ArgsSchema = v.tupleWithRest(
[v.string()], // first arg is string
v.number(), // rest are numbers
);
// Type: [string, ...number[]]typescript
// 固定长度数组,每个元素类型指定
const CoordinatesSchema = v.tuple([v.number(), v.number()]);
// 类型:[number, number]
// 带剩余元素的元组
const ArgsSchema = v.tupleWithRest(
[v.string()], // 第一个参数是字符串
v.number(), // 剩余参数是数值
);
// 类型:[string, ...number[]]Unions and Variants
联合类型和变体类型
Union
联合类型
typescript
const StringOrNumberSchema = v.union([v.string(), v.number()]);
const StatusSchema = v.union([
v.literal("pending"),
v.literal("active"),
v.literal("inactive"),
]);typescript
const StringOrNumberSchema = v.union([v.string(), v.number()]);
const StatusSchema = v.union([
v.literal("pending"),
v.literal("active"),
v.literal("inactive"),
]);Picklist (for string/number literals)
枚举列表(适用于字符串/数值字面量)
typescript
// Simpler than union of literals
const StatusSchema = v.picklist(["pending", "active", "inactive"]);
const PrioritySchema = v.picklist([1, 2, 3]);typescript
// 比字面量联合类型更简洁
const StatusSchema = v.picklist(["pending", "active", "inactive"]);
const PrioritySchema = v.picklist([1, 2, 3]);Variant (discriminated union)
变体类型(区分联合类型)
Use for better performance with discriminated unions:
varianttypescript
const EventSchema = v.variant("type", [
v.object({
type: v.literal("click"),
x: v.number(),
y: v.number(),
}),
v.object({
type: v.literal("keypress"),
key: v.string(),
}),
v.object({
type: v.literal("scroll"),
direction: v.picklist(["up", "down"]),
}),
]);对于区分联合类型,使用可获得更好的性能:
varianttypescript
const EventSchema = v.variant("type", [
v.object({
type: v.literal("click"),
x: v.number(),
y: v.number(),
}),
v.object({
type: v.literal("keypress"),
key: v.string(),
}),
v.object({
type: v.literal("scroll"),
direction: v.picklist(["up", "down"]),
}),
]);Parsing Data
数据解析
parse() — Throws on Error
parse() — 验证失败时抛出错误
typescript
import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
try {
const email = v.parse(EmailSchema, "jane@example.com");
console.log(email); // 'jane@example.com'
} catch (error) {
console.error(error); // ValiError
}typescript
import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
try {
const email = v.parse(EmailSchema, "jane@example.com");
console.log(email); // 'jane@example.com'
} catch (error) {
console.error(error); // ValiError 错误对象
}safeParse() — Returns Result Object
safeParse() — 返回结果对象
typescript
const result = v.safeParse(EmailSchema, input);
if (result.success) {
console.log(result.output); // Valid data
} else {
console.log(result.issues); // Array of issues
}typescript
const result = v.safeParse(EmailSchema, input);
if (result.success) {
console.log(result.output); // 验证通过的数据
} else {
console.log(result.issues); // 错误信息数组
}is() — Type Guard
is() — 类型守卫
typescript
if (v.is(EmailSchema, input)) {
// input is typed as string
}typescript
if (v.is(EmailSchema, input)) {
// 此处input会被推断为string类型
}Configuration Options
配置选项
typescript
// Abort early - stop at first error
v.parse(Schema, data, { abortEarly: true });
// Abort pipe early - stop pipeline at first error
v.parse(Schema, data, { abortPipeEarly: true });typescript
// 提前终止验证——遇到第一个错误就停止
v.parse(Schema, data, { abortEarly: true });
// 提前终止管道——管道中遇到第一个错误就停止
v.parse(Schema, data, { abortPipeEarly: true });Type Inference
类型推断
typescript
import * as v from "valibot";
const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
role: v.optional(v.string(), "user"),
});
// Output type (after transformations and defaults)
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number; role: string }
// Input type (before transformations)
type UserInput = v.InferInput<typeof UserSchema>;
// { name: string; age: string; role?: string | undefined }
// Issue type
type UserIssue = v.InferIssue<typeof UserSchema>;typescript
import * as v from "valibot";
const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
role: v.optional(v.string(), "user"),
});
// 输出类型(转换和默认值应用后的类型)
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number; role: string }
// 输入类型(转换前的类型)
type UserInput = v.InferInput<typeof UserSchema>;
// { name: string; age: string; role?: string | undefined }
// 错误信息类型
type UserIssue = v.InferIssue<typeof UserSchema>;Error Handling
错误处理
Custom Error Messages
自定义错误信息
typescript
const LoginSchema = v.object({
email: v.pipe(
v.string("Email must be a string"),
v.nonEmpty("Please enter your email"),
v.email("Invalid email format"),
),
password: v.pipe(
v.string("Password must be a string"),
v.nonEmpty("Please enter your password"),
v.minLength(8, "Password must be at least 8 characters"),
),
});typescript
const LoginSchema = v.object({
email: v.pipe(
v.string("邮箱必须是字符串类型"),
v.nonEmpty("请输入你的邮箱"),
v.email("邮箱格式无效"),
),
password: v.pipe(
v.string("密码必须是字符串类型"),
v.nonEmpty("请输入你的密码"),
v.minLength(8, "密码长度至少为8个字符"),
),
});Flattening Errors
扁平化错误信息
typescript
const result = v.safeParse(LoginSchema, data);
if (!result.success) {
const flat = v.flatten(result.issues);
// { nested: { email: ['Invalid email format'], password: ['...'] } }
}typescript
const result = v.safeParse(LoginSchema, data);
if (!result.success) {
const flat = v.flatten(result.issues);
// { nested: { email: ['邮箱格式无效'], password: ['...'] } }
}Issue Structure
错误信息结构
Each issue contains:
- : 'schema' | 'validation' | 'transformation'
kind - : Function name (e.g., 'string', 'email', 'min_length')
type - : The problematic input
input - : What was expected
expected - : What was received
received - : Human-readable message
message - : Array of path items for nested issues
path
每个错误信息包含以下字段:
- : 'schema' | 'validation' | 'transformation'(错误类型)
kind - : 函数名称(如'string', 'email', 'min_length')
type - : 引发错误的输入值
input - : 预期的内容
expected - : 实际接收到的内容
received - : 人类可读的错误提示
message - : 嵌套错误的路径数组
path
Fallback Values
回退值
typescript
// Static fallback
const NumberSchema = v.fallback(v.number(), 0);
v.parse(NumberSchema, "invalid"); // Returns 0
// Dynamic fallback
const DateSchema = v.fallback(v.date(), () => new Date());typescript
// 静态回退值
const NumberSchema = v.fallback(v.number(), 0);
v.parse(NumberSchema, "invalid"); // 返回0
// 动态回退值
const DateSchema = v.fallback(v.date(), () => new Date());Recursive Schemas
递归Schema
typescript
import * as v from "valibot";
type TreeNode = {
value: string;
children: TreeNode[];
};
const TreeNodeSchema: v.GenericSchema<TreeNode> = v.object({
value: v.string(),
children: v.lazy(() => v.array(TreeNodeSchema)),
});typescript
import * as v from "valibot";
type TreeNode = {
value: string;
children: TreeNode[];
};
const TreeNodeSchema: v.GenericSchema<TreeNode> = v.object({
value: v.string(),
children: v.lazy(() => v.array(TreeNodeSchema)),
});Async Validation
异步验证
For async operations (e.g., database checks), use async variants:
typescript
import * as v from "valibot";
const isUsernameAvailable = async (username: string) => {
// Check database
return true;
};
const UsernameSchema = v.pipeAsync(
v.string(),
v.minLength(3),
v.checkAsync(isUsernameAvailable, "Username is already taken"),
);
// Must use parseAsync
const username = await v.parseAsync(UsernameSchema, "john");对于异步操作(如数据库检查),使用异步版本的API:
typescript
import * as v from "valibot";
const isUsernameAvailable = async (username: string) => {
// 数据库检查逻辑
return true;
};
const UsernameSchema = v.pipeAsync(
v.string(),
v.minLength(3),
v.checkAsync(isUsernameAvailable, "该用户名已被占用"),
);
// 必须使用parseAsync进行解析
const username = await v.parseAsync(UsernameSchema, "john");JSON Schema Conversion
JSON Schema转换
typescript
import { toJsonSchema } from "@valibot/to-json-schema";
import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
const jsonSchema = toJsonSchema(EmailSchema);
// { type: 'string', format: 'email' }typescript
import { toJsonSchema } from "@valibot/to-json-schema";
import * as v from "valibot";
const EmailSchema = v.pipe(v.string(), v.email());
const jsonSchema = toJsonSchema(EmailSchema);
// { type: 'string', format: 'email' }Naming Conventions
命名规范
Convention 1: Same Name (Recommended for simplicity)
规范1:同名(推荐,简洁明了)
typescript
export const User = v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
});
export type User = v.InferOutput<typeof User>;
// Usage
const users: User[] = [];
users.push(v.parse(User, data));typescript
export const User = v.object({
name: v.string(),
email: v.pipe(v.string(), v.email()),
});
export type User = v.InferOutput<typeof User>;
// 使用示例
const users: User[] = [];
users.push(v.parse(User, data));Convention 2: With Suffixes (Recommended when input/output differ)
规范2:带后缀(输入输出类型不同时推荐)
typescript
export const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
});
export type UserInput = v.InferInput<typeof UserSchema>;
export type UserOutput = v.InferOutput<typeof UserSchema>;typescript
export const UserSchema = v.object({
name: v.string(),
age: v.pipe(v.string(), v.transform(Number)),
});
export type UserInput = v.InferInput<typeof UserSchema>;
export type UserOutput = v.InferOutput<typeof UserSchema>;Common Patterns
常见使用场景
Login Form
登录表单验证
typescript
const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty("Please enter your email"),
v.email("Invalid email address"),
),
password: v.pipe(
v.string(),
v.nonEmpty("Please enter your password"),
v.minLength(8, "Password must be at least 8 characters"),
),
});typescript
const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty("请输入你的邮箱"),
v.email("邮箱地址无效"),
),
password: v.pipe(
v.string(),
v.nonEmpty("请输入你的密码"),
v.minLength(8, "密码长度至少为8个字符"),
),
});API Response
API响应验证
typescript
const ApiResponseSchema = v.variant("status", [
v.object({
status: v.literal("success"),
data: v.unknown(),
}),
v.object({
status: v.literal("error"),
error: v.object({
code: v.string(),
message: v.string(),
}),
}),
]);typescript
const ApiResponseSchema = v.variant("status", [
v.object({
status: v.literal("success"),
data: v.unknown(),
}),
v.object({
status: v.literal("error"),
error: v.object({
code: v.string(),
message: v.string(),
}),
}),
]);Environment Variables
环境变量验证
typescript
const EnvSchema = v.object({
NODE_ENV: v.picklist(["development", "production", "test"]),
PORT: v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1)),
DATABASE_URL: v.pipe(v.string(), v.url()),
API_KEY: v.pipe(v.string(), v.minLength(32)),
});
const env = v.parse(EnvSchema, process.env);typescript
const EnvSchema = v.object({
NODE_ENV: v.picklist(["development", "production", "test"]),
PORT: v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1)),
DATABASE_URL: v.pipe(v.string(), v.url()),
API_KEY: v.pipe(v.string(), v.minLength(32)),
});
const env = v.parse(EnvSchema, process.env);Date Handling
日期处理
typescript
// String to Date
const DateFromStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// Date validation
const FutureDateSchema = v.pipe(
v.date(),
v.minValue(new Date(), "Date must be in the future"),
);typescript
// 字符串转Date对象
const DateFromStringSchema = v.pipe(
v.string(),
v.isoDate(),
v.transform((input) => new Date(input)),
);
// 验证日期是否为未来时间
const FutureDateSchema = v.pipe(
v.date(),
v.minValue(new Date(), "日期必须是未来时间"),
);