Valibot 中异步自定义验证函数的序列化问题解析
2025-05-30 09:59:18作者:劳婵绚Shirley
在 Valibot 这个强大的 TypeScript 模式验证库中,开发者经常会遇到需要实现异步自定义验证逻辑的场景。本文将通过一个典型的使用案例,深入分析异步验证函数的序列化问题及其解决方案。
异步验证的典型场景
在用户注册表单验证中,我们通常需要检查邮箱的唯一性。Valibot 提供了 customAsync 验证器来实现这一需求:
export const insertUserSchema = objectAsync({
email: stringAsync([
email(),
customAsync(uniqueEmail, 'Email Adresse schon vorhanden')
]),
// 其他字段...
});
其中 uniqueEmail 是一个异步验证函数,它会查询数据库检查邮箱是否已存在:
export const uniqueEmail = async (email: string): Promise<boolean> => {
const result = await db.query.userTable.findFirst({
where: (userTable, { eq }) => eq(userTable.email, email)
});
return !result;
};
问题现象
当使用 safeParseAsync 进行验证并处理错误时,开发者可能会尝试将验证错误对象直接返回给客户端:
const parsedValues = await safeParseAsync(schema, formValues);
if (!parsedValues.success) {
return fail(403, { issues: parsedValues.issues });
}
这时会遇到序列化错误,因为验证错误对象中的 requirement 属性保留了原始的验证函数:
{
reason: 'string',
validation: 'custom',
message: 'Email Adresse schon vorhanden',
input: 'xxxxxx',
requirement: [AsyncFunction (anonymous)], // 无法序列化的函数
// ...
}
问题本质
这种现象是 Valibot 的预期行为。验证错误对象中的 requirement 属性始终保存着原始的验证条件:
- 对于
email验证,它是正则表达式 - 对于
custom验证,它就是开发者传入的验证函数
这不是一个 bug,而是设计如此,因为 Valibot 需要完整保留验证的上下文信息。
解决方案
方案一:过滤非序列化属性
最简单的解决方案是在返回给客户端前过滤掉函数属性:
const formatValidationIssues = (issues: Issue[]) => {
return issues.map(({ requirement, skipPipe, ...rest }) => rest);
};
方案二:使用 Valibot 的 flatten 方法
Valibot 提供了内置的 flatten 方法来格式化验证错误:
import { flatten } from 'valibot';
const formattedErrors = flatten(parsedValues.error);
方案三:仅返回必要信息
最佳实践是只返回客户端真正需要的信息,通常是错误消息和路径:
const simplifiedIssues = parsedValues.issues.map(issue => ({
path: issue.path,
message: issue.message
}));
进阶建议
对于需要在客户端和服务端共享但又有差异的验证逻辑,可以采用以下模式:
- 定义基础模式(客户端和服务端共用)
- 在服务端扩展添加服务端特有的验证
// 基础模式(客户端和服务端共用)
const baseUserSchema = objectAsync({
username: string([/* 客户端验证规则 */]),
// ...
});
// 服务端扩展模式
const serverUserSchema = mergeAsync([
baseUserSchema,
objectAsync({
email: stringAsync([email(), customAsync(uniqueEmail)])
})
]);
总结
Valibot 的验证错误对象设计保留了完整的验证上下文,这虽然会导致序列化问题,但也提供了最大的灵活性。开发者应当根据实际需求选择适当的错误格式化策略,而不是直接返回原始错误对象。理解这一设计理念,能够帮助开发者更好地利用 Valibot 构建健壮的验证逻辑。
登录后查看全文
热门项目推荐
相关项目推荐
AutoGLM-Phone-9BAutoGLM-Phone-9B是基于AutoGLM构建的移动智能助手框架,依托多模态感知理解手机屏幕并执行自动化操作。Jinja00
Kimi-K2-ThinkingKimi K2 Thinking 是最新、性能最强的开源思维模型。从 Kimi K2 开始,我们将其打造为能够逐步推理并动态调用工具的思维智能体。通过显著提升多步推理深度,并在 200–300 次连续调用中保持稳定的工具使用能力,它在 Humanity's Last Exam (HLE)、BrowseComp 等基准测试中树立了新的技术标杆。同时,K2 Thinking 是原生 INT4 量化模型,具备 256k 上下文窗口,实现了推理延迟和 GPU 内存占用的无损降低。Python00
GLM-4.6V-FP8GLM-4.6V-FP8是GLM-V系列开源模型,支持128K上下文窗口,融合原生多模态函数调用能力,实现从视觉感知到执行的闭环。具备文档理解、图文生成、前端重构等功能,适用于云集群与本地部署,在同类参数规模中视觉理解性能领先。Jinja00
HunyuanOCRHunyuanOCR 是基于混元原生多模态架构打造的领先端到端 OCR 专家级视觉语言模型。它采用仅 10 亿参数的轻量化设计,在业界多项基准测试中取得了当前最佳性能。该模型不仅精通复杂多语言文档解析,还在文本检测与识别、开放域信息抽取、视频字幕提取及图片翻译等实际应用场景中表现卓越。00
GLM-ASR-Nano-2512GLM-ASR-Nano-2512 是一款稳健的开源语音识别模型,参数规模为 15 亿。该模型专为应对真实场景的复杂性而设计,在保持紧凑体量的同时,多项基准测试表现优于 OpenAI Whisper V3。Python00
GLM-TTSGLM-TTS 是一款基于大语言模型的高质量文本转语音(TTS)合成系统,支持零样本语音克隆和流式推理。该系统采用两阶段架构,结合了用于语音 token 生成的大语言模型(LLM)和用于波形合成的流匹配(Flow Matching)模型。 通过引入多奖励强化学习框架,GLM-TTS 显著提升了合成语音的表现力,相比传统 TTS 系统实现了更自然的情感控制。Python00
Spark-Formalizer-X1-7BSpark-Formalizer 是由科大讯飞团队开发的专用大型语言模型,专注于数学自动形式化任务。该模型擅长将自然语言数学问题转化为精确的 Lean4 形式化语句,在形式化语句生成方面达到了业界领先水平。Python00
项目优选
收起
deepin linux kernel
C
24
9
Ascend Extension for PyTorch
Python
222
238
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
9
1
暂无简介
Dart
671
156
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
661
312
React Native鸿蒙化仓库
JavaScript
261
322
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Java
64
19
仓颉编译器源码及 cjdb 调试工具。
C++
134
867
仓颉编程语言测试用例。
Cangjie
37
859
openGauss kernel ~ openGauss is an open source relational database management system
C++
160
217