- 后端
- 前端
【免费下载链接】valibot
The modular and type safe schema library for validating structural data 🤖
@valibot/to-json-schema是 Valibot 官方生态中用于把 Valibot 校验 schema 转换为 JSON Schema 的独立工具包,支持 JSON Schema draft-07、draft-2020-12 与 OpenAPI 3.0 Schema Object 三种目标格式。本文以该包在仓库中的 CHANGELOG.md 为主轴,结合 README.md 与src/下真实源码,完整梳理其从 2024 年初版到 2026 年 v1.8.0 的功能演进、配置项语义、三个公开转换函数的使用方式,以及转换器底层的管道切片、引用管理、约束合并与错误处理实现原理。读完本文,你将能掌握如何在 API 文档生成、代码生成、LLM 结构化输出等场景中正确配置并深度定制该转换器,同时理解它的能力边界与版本升级要点。
一、包定位与典型应用场景
Valibot 是一个模块化、类型安全的 schema 校验库,而@valibot/to-json-schema是其官方提供的 JSON Schema 转换器(见 package.json 中的描述 "The official JSON schema converter for Valibot")。一次调用即可完成转换:
import { toJsonSchema } from '@valibot/to-json-schema'; import * as v from 'valibot'; toJsonSchema(v.string()); // { $schema: "http://json-schema.org/draft-07/schema#", type: "string" }根据 README.md,该包最典型的落地场景包括:
- API 文档生成:从 Valibot schema 直接生成 OpenAPI 规范;
- 代码生成:由校验 schema 生成客户端 SDK 与类型;
- LLM 集成:为大型语言模型生成结构化输出约束;
- 跨语言共享:把同一套校验逻辑共享到不同编程语言。
需要特别注意:并非所有 Valibot 特性都能映射到 JSON Schema。例如转换类 action(transform等)在 JSON Schema 中没有对应物,部分过于依赖 JavaScript 的 schema 或校验也没有等价的 JSON Schema 属性。
二、版本演进地图:从 v0.1.0 到 v1.8.0
CHANGELOG.md 完整记录了 14 个版本。按能力演进可划分为五个阶段:
| 阶段 | 版本 | 时间 | 核心变化 |
|---|---|---|---|
| 起步期 | v0.1.0 ~ v0.2.1 | 2024-09 ~ 2024-09 | 初版发布、递归 schema 修复、titleaction、Deno 类型导出修复 |
| 正式版 | v1.0.0 | 2025-03 | errorMode取代force、exactOptional/undefinedable、首批字符串格式 action |
| 定义与定制 | v1.1.0 ~ v1.3.0 | 2025-05 ~ 2025-06 | definitions、ignoreActions、typeMode、override 三件套、toJsonSchemaDefs、全局定义 |
| 现代规格 | v1.4.0 ~ v1.5.0 | 2025-12 | draft-2020-12 与 OpenAPI 3.0、toStandardJsonSchema、自定义JsonSchema类型 |
| 边界补齐 | v1.6.0 ~ v1.8.0 | 2026-03 ~ 2026-09 | never、18 个新 action、JSON 兼容性校验、重叠约束取最严、$refJSON Pointer 编码 |
1. 起步期(v0.1.0 – v0.2.1):递归 schema 是首个攻坚点
v0.1.0 为初版发布;v0.1.1 修复了递归 schema 的最大调用栈溢出(maximum call stack bug)与递归 schema 输出非法 JSON Schema 的问题。这两项修复奠定了lazyschema 与 definitions 机制的早期基础。v0.2.0 增加titleaction(讨论 #826),v0.2.1 修复 Deno 下的类型导出。
2. 正式版 v1.0.0:errorMode 与规模化 action 支持
v1.0.0 是首个 1.0 版本,Valibot peer 依赖同步升级到 v1.0.0,包含三类结构性变化:
- 新增 schema:
exactOptional、undefinedable; - 新增 action:
base64、isoTime、isoDateTime、nonEmpty、url(PR #962),以及bic、cuid2、empty、decimal、digits、emoji、hexColor、hexadecimal、nanoid、octal、ulid(PR #998); - 配置语义调整:
force更名为errorMode(issue #889),object/looseObject的additionalProperties输出行为调整(PR #1001),并从nullable、nullish、optionalschema 中提取默认值。
3. 定义与定制(v1.1.0 – v1.3.0):可编程化转型
- v1.1.0:支持
minEntries/maxEntries(PR #1100)与entries(PR #1156);修复toJsonSchema对定义顺序的依赖(PR #1133);修复 tuple schema 的additionalItems并新增minItems(PR #1126)。 - v1.2.0:
metadataaction 开始支持 title、description、examples(PR #1189);新增三类 override 配置(PR #1197);新增全局定义存储addGlobalDefs/getGlobalDefs与独立的toJsonSchemaDefs函数(PR #1197)。 - v1.3.0:新增
ignoreActions与typeMode两个关键配置;把ConversionContext、OverrideSchemaContext、OverrideActionContext、OverrideRefContext纳入公开导出;构建工具切换为 tsdown + Rolldown。
4. 现代规格支持(v1.4.0 – v1.5.0)
- v1.4.0:新增
examplesaction;integer可与minValue/maxValue组合(PR #1367);修复exactOptional对象属性转换(PR #1220);variant从anyOf改为oneOf输出(PR #1193)。 - v1.5.0:支持 JSON Schema draft-2020-12 与 OpenAPI 3.0 Schema Object 两种新目标格式;record schema 增加
propertyNames用于键校验约束;toBigint、toBoolean、toDate、toNumber、toString在typeMode: 'input'下可用;新增toStandardJsonSchema函数;返回类型从JSONSchema7换为自定义JsonSchema类型——这是一次对下游代码有感知的破坏性类型变更。
5. 边界补齐(v1.6.0 – v1.8.0):兼容性校验与最严约束合并
- v1.6.0:新增
neverschema,以及endsWith、gtValue、hash、includes、isoTimeSecond、isoWeek、isrc、ltValue、mac、mac48、mac64、notValue、notValues、rfcEmail、safeInteger、slug、startsWith、values共 18 个 action(PR #1430);对value/values/notValue/notValues的 requirement 增加 JSON 兼容性校验;为enum/picklist推断type字段。 - v1.7.0:构建目标改为 ES2020,保证产物在缺少新语法支持的环境仍可运行;peer 依赖升到 v1.4.0。
- v1.7.1:修复
$ref生成,把定义键中的/与~按 JSON Pointer 规范编码为~1与~0(PR #1482)。 - v1.8.0(当前最新):新增
ksuidaction(PR #1370);metadataaction 开始透传其余属性以支持自定义注解与format等标准关键字(PR #1591);围绕重叠约束、NaN/无穷值、非负整数 requirement、lazy引用 ID 一致性等做了一大批健壮性修复(详见下文第六节),peer 依赖升到 v1.5.0。
三、配置体系完整指南
转换配置由 config.ts 中的ConversionConfig接口定义,全部为可选字段:
| 配置项 | 类型 | 说明 |
|---|---|---|
target | 'draft-07' \| 'draft-2020-12' \| 'openapi-3.0' | 目标 JSON Schema 格式,默认'draft-07' |
typeMode | 'ignore' \| 'input' \| 'output' | 转换 schema 的输入类型还是输出类型,默认'ignore'(转换整个管道) |
errorMode | 'throw' \| 'warn' \| 'ignore' | 遇到不兼容 schema/action 时的处理策略,默认'throw' |
definitions | Record<string, GenericSchema> | 用于构造递归 schema 的定义;不传则自动生成 |
overrideSchema | (context: OverrideSchemaContext) => JsonSchema \| null \| undefined | 覆盖某个 Valibot schema 的 JSON Schema 转换 |
ignoreActions | string[] | 转换时忽略指定 action |
overrideAction | (context: OverrideActionContext) => JsonSchema \| null \| undefined | 覆盖某个 Valibot action 的转换结果 |
overrideRef | (context: OverrideRefContext) => string \| null \| undefined | 覆盖某个引用 ID 的$ref输出 |
1. target:三种目标格式的语法差异
同一 schema 在不同 target 下产物不同。例如v.nullable(v.string()):
// JSON Schema draft-07(默认) toJsonSchema(schema); // { $schema: "http://json-schema.org/draft-07/schema#", anyOf: [{ type: "string" }, { type: "null" }] } // JSON Schema draft-2020-12 toJsonSchema(schema, { target: 'draft-2020-12' }); // { $schema: "https://json-schema.org/draft/2020-12/schema", anyOf: [{ type: "string" }, { type: "null" }] } // OpenAPI 3.0 Schema Object toJsonSchema(schema, { target: 'openapi-3.0' }); // { type: "string", nullable: true }从 toJsonSchema.ts 源码可以看到,$schema只对 draft-07 与 draft-2020-12 写入(OpenAPI 3.0 没有$schema属性)。此外不同 target 还影响 tuple、record、literal等结构的输出方式(见第五节)。
2. typeMode:输入类型与输出类型
typeMode用于控制转换输入类型还是输出类型,在定义 API 端点时尤其有用——外部开发者对请求体和响应体需要不同的 schema 信息:
'input':转换在管道中第一个潜在类型转换 action 或第二个 schema 之前停止;'output':从管道中最后一个 schema 开始转换,因此输出类型必须显式地用最后一个转换 action 之后的 schema 指定;'ignore'(默认):转换整个管道。
const ValibotSchema = v.pipe( v.string(), v.decimal(), v.transform(Number), v.number(), v.maxValue(100) ); toJsonSchema(ValibotSchema, { typeMode: 'input' }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "string", // pattern: "^[+-]?(?:\\d*\\.)?\\d+$" // } toJsonSchema(ValibotSchema, { typeMode: 'output' }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "number", // maximum: 100 // }其实现核心在 convertSchema.ts:先把嵌套管道递归展开(flattenPipe),再根据typeMode计算startIndex/stopIndex切片区间。'input'模式会查找第一个kind === 'schema'或类型转换类 action(transform、to_bigint、to_date等 10 种)的位置并截断;'output'模式则用findLastIndex找到最后一个 schema 作为起点。注意:若管道中存在第二个 schema 且未设置typeMode,转换器会报错提示设置"typeMode"为"input"或"output"。
3. errorMode:三种不兼容处理策略
errorMode决定转换器如何处理不支持的 schema 与 action,默认'throw':
toJsonSchema(v.file(), { errorMode: 'ignore' }); // {} toJsonSchema(v.pipe(v.string(), v.creditCard()), { errorMode: 'ignore' }); // { type: "string" }不支持的 schema 通常返回空 JSON Schema({}),不支持的 action 通常被忽略。'warn'模式则打印警告后继续。该机制在 convertSchema.ts 与 convertAction.ts 末尾统一通过handleError(message, config)执行。
4. overrideSchema / overrideAction / overrideRef:深度定制
overrideSchema可处理不支持的 schema 或定制转换行为,返回null/undefined表示跳过覆盖:
const ValibotSchema = v.object({ createdAt: v.date() }); toJsonSchema(ValibotSchema, { overrideSchema(context) { if (context.valibotSchema.type === 'date') { return { type: 'string', format: 'date-time' }; } }, }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "object", // properties: { createdAt: { type: "string", format: "date-time" } }, // required: ["createdAt"] // }overrideRef可定制引用 ID,典型场景是 OpenAPI 的#/components/schemas/...路径:
import { toJsonSchemaDefs } from '@valibot/to-json-schema'; import * as v from 'valibot'; const UserSchema = v.object({ name: v.string() }); toJsonSchemaDefs( { UserSchema }, { overrideRef: (context) => `#/schemas/${context.referenceId}` } );从源码看,overrideSchema/overrideAction的返回值会整体替换默认转换结果(return { ...schemaOverride }),而overrideRef只改写jsonSchema.$ref。
5. definitions:手动提供递归定义
嵌套与递归 schema 可拆分为多个可复用定义:
const EmailSchema = v.pipe(v.string(), v.email()); toJsonSchema(v.object({ email: EmailSchema }), { definitions: { EmailSchema }, }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "object", // properties: { email: { $ref: "#/$defs/EmailSchema" } }, // required: ["email"], // $defs: { EmailSchema: { type: "string", format: "email" } } // }definitions不是转换lazyschema 的必要条件——缺失的定义会自动生成,例如v.lazy(() => StringSchema)会被分配自动 ID0,输出#/$defs/0。
四、三个公开转换函数与全局定义
包的公开 API 由 functions/index.ts 导出三个转换函数,配合 storages/globalDefs 的全局定义存储。
1. toJsonSchema:完整转换入口
内部执行流程(toJsonSchema.ts):
- 初始化
ConversionContext:definitions空对象、referenceMap(ReferenceMap实例)、getterMap; - 从
config.definitions或getGlobalDefs()取定义,把每个定义的 schema 注册进referenceMap并预转换进context.definitions; - 调用
convertSchema转换目标 schema; - 按
target写入$schema; - 若存在引用,写入
$defs。
2. toJsonSchemaDefs:只输出定义
只把提供的 Valibot schema 定义转换为 JSON Schema 定义、不包根 schema,适用于 OpenAPI 规范中只需要 schema components 的场景:
const EmailSchema = v.pipe(v.string(), v.email()); const UserSchema = v.object({ name: v.string(), email: EmailSchema }); toJsonSchemaDefs({ EmailSchema, UserSchema }); // { // EmailSchema: { type: "string", format: "email" }, // UserSchema: { // type: "object", // properties: { // name: { type: "string" }, // email: { $ref: "#/$defs/EmailSchema" } // }, // required: ["name", "email"] // } // }注意其配置类型是Omit<ConversionConfig, 'definitions'>,即不能再传definitions。
3. toStandardJsonSchema:接入 Standard JSON Schema 生态
将 Valibot schema 转换为 Standard JSON Schema 源码可以看到,它返回带~standard键的对象,其中jsonSchema.input/output两个方法分别以typeMode: 'input'/'output'调用toJsonSchema,且只接受draft-07、draft-2020-12、openapi-3.0三种 target,否则抛Unsupported target错误。
4. addGlobalDefs / getGlobalDefs:全局定义存储
适合大型项目中大量可复用 schema 的场景,注册后转换时自动生效:
addGlobalDefs({ ValibotSchema1, ValibotSchema2 }); toJsonSchema(ValibotSchema3); // { ..., $defs: { ValibotSchema1: { type: "string" }, ValibotSchema2: { type: "number" } } }也可用getGlobalDefs()取回全局定义,再交给toJsonSchemaDefs直接转换。
五、转换器底层实现原理
1. 管道展开与 typeMode 切片
convertSchema.ts 的flattenPipe会递归展开嵌套管道;随后按typeMode计算起止下标。管道内的 schema 项以skipRef = true递归转换(防止后续管道元素改变 schema 导致引用失效),action 项交给convertAction。
2. ReferenceMap 与引用 ID 防碰撞
ReferenceMap.ts 是一个继承自Map的专用类,set时把 ID 记入usedIds;createId用自增计数器生成未被占用、且不与现有definitions键冲突的 ID。这正是 v1.8.0 中"为lazyschema 生成一致的引用 ID 并避免与已有定义冲突"这一修复的实现载体;v1.8.0 同时把ConversionContext.referenceMap的类型从Map收窄为该ReferenceMap子类。
3. schema 转换矩阵(switch 分支)
convertSchema对约 30 种 schema 做switch分发,关键差异包括:
- tuple 系列(
tuple、tuple_with_rest、loose_tuple、strict_tuple):draft-07 用items数组 +additionalItems;draft-2020-12 用prefixItems+items;OpenAPI 3.0 用items.anyOf+minItems/maxItems; - nullable/nullish:OpenAPI 3.0 下合并内层 schema 并设
nullable: true,其余格式用anyOf: [内层, { type: "null" }],且默认值会被求值写入default; - literal:draft 系列用
const,OpenAPI 3.0 用enum(因为 OpenAPI 不支持const),并校验值是否 JSON 兼容; - enum/picklist:校验所有选项为字符串或有限数值,全字符串推断
type: "string",全数值推断type: "number",混合类型在非 OpenAPI 下输出type: ['string', 'number']; - record:键必须为
stringschema(否则报错),非 OpenAPI 目标下用propertyNames表达键约束; - variant:输出
oneOf(v1.4.0 起);intersect输出allOf;union输出anyOf; - never:输出
not: {};any/unknown:输出空 schema; - lazy:通过
getterMap保证每个 getter 只解包一次,为解包出的 schema 分配引用 ID 并写入$defs,最终输出$ref。
4. action 转换与约束合并策略
convertAction.ts 内置三个约束合并工具:
getLowerBound/getUpperBound:取更严的边界,同时用Number.isFinite替换无法在 JSON 中表达的无穷边界——这正是 v1.8.0"重叠数值/长度/对象条目边界保留更严约束"的实现基础;getNotRestriction:把多次not约束合并为anyOf取反,避免相互覆盖;intersectEnum:对已有enum与新限制取交集去重,交集为空时删除enum并转为not: {}(不可能 schema)。
此外:正则类 action(regex、starts_with等)在已存在pattern时报告冲突;regex带 flags 会报"RegExp flags 不被 JSON Schema 支持";metadataaction 会把title/description/examples映射到标准关键字,其余属性(除__proto__防原型污染外)直接透传到 JSON Schema——这就是 v1.8.0 支持通过metadata添加format等标准关键字与x-自定义注解的原理:
const ValibotSchema = v.pipe( v.string(), v.email(), v.metadata({ title: 'Email Schema', description: 'A schema that validates email addresses.', examples: ['jane@example.com'], 'x-category': 'auth', }) ); toJsonSchema(ValibotSchema); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "string", // format: "email", // title: "Email Schema", // description: "A schema that validates email addresses.", // examples: ["jane@example.com"], // "x-category": "auth" // }5. JSON 兼容性校验与错误分级
v1.6.0 起为literal、enum、picklist、value、values、notValue、notValues等引入 JSON 兼容性校验(isJsonConstValue、isJsonEnumValues工具),拒绝NaN、无穷值等无法表示的常量。v1.8.0 进一步细化了错误策略:默认('throw')直接拒绝NaN/无穷 requirement 与非负整数校验失败;而在'warn'/'ignore'模式下跳过这些无效约束继续转换,同时跳过不支持类型上的数值约束与不支持的literal/enum/picklist值。
六、能力边界与注意事项
1. schema / action 支持矩阵
根据 README.md 的支持矩阵,schema 方面any、array、boolean、exactOptional、intersect、looseObject、looseTuple、never、null、nullable、nullish、number、objectWithRest、object、optional、strictObject、strictTuple、string、tupleWithRest、tuple、union、undefinedable、unknown均完全支持;enum/picklist/literal仅支持 JSON 兼容值;lazy的 getter 始终以undefined作为输入执行;record仅支持string键 schema 且propertyNames在 OpenAPI 3.0 不可用;variant的判别键会被忽略。
action 方面,base64、bic、cuid2、decimal、description、digits、domain、email、emoji、empty、endsWith、entries、examples、hash、hexadecimal、hexColor、includes、integer、ipv4、ipv6、isoDate、isoDateTime、isoTime、isoTimeSecond、isoTimestamp、isoWeek、isrc、jwsCompact、ksuid、mac、mac48、mac64、maxEntries、maxLength、metadata、minEntries、minLength、multipleOf、nanoid、nonEmpty、notValue、notValues、octal、rfcEmail、safeInteger、slug、startsWith、title、ulid、url、uuid、value、values均受支持;length/minLength/maxLength仅与string、array组合;minValue/maxValue仅与number组合;gtValue/ltValue仅与number、integer组合;regex不支持 RegExp flags;notValue/notValues/value/values仅支持 JSON 兼容值。
2. 字符串长度语义差异
length/minLength/maxLength对字符串的语义在 Valibot 与 JSON Schema 之间存在差异:Valibot 按 JavaScript 字符串长度(value.length,即 UTF-16 码元)校验,而 JSON Schema 的minLength/maxLength按 Unicode 码点计数。对 emoji 等非 BMP 码点、以及组合标记或 ZWJ 序列等多码点字素簇,二者结果可能不同。
3. OpenAPI 3.0 的降级手法
由于 OpenAPI 3.0 缺少部分 JSON Schema 关键字,转换器使用了多种 workaround(见 convertSchema.ts 中的注释):null类型用enum: [null]表达;literal用enum代替const;nullable用nullable: true;record不输出propertyNames;混合类型的enum不输出多类型type数组。
七、升级要点与实践建议
- peer 依赖对应关系:v1.0.0 → valibot ^1.0.0,v1.1.0 → ^1.1.0,v1.2.0 → ^1.2.0,v1.3.0 → ^1.3.0,v1.4.0 → ^1.4.0,v1.5.0 → ^1.5.0,当前 v1.8.0 要求 package.json 中的
"valibot": "^1.5.0"。 - 从旧版本升级的破坏性变化:v1.5.0 将返回类型从
JSONSchema7换成自定义JsonSchema类型,依赖旧类型签名的代码需要同步调整;v1.0.0 把配置项force改名errorMode,旧配置需迁移。 - 构建产物:v1.7.0 起构建目标为 ES2020,产物同时提供 ESM(
dist/index.mjs)与 CJS(dist/index.cjs)入口,且sideEffects: false便于 tree-shaking。 - 实操建议:定义 API 端点时优先组合
typeMode(请求用'input'、响应用'output');处理自定义类型(如date、file)时用overrideSchema;OpenAPI 集成时用overrideRef生成#/components/schemas/...引用;大型项目用addGlobalDefs统一管理可复用定义;把errorMode设为'warn'可快速发现哪些 schema 无法映射,再针对性 override。
总而言之,@valibot/to-json-schema从初版到 v1.8.0 的演进清晰体现了"覆盖面扩张 → 可编程定制 → 现代规格对齐 → 约束语义精化"四条主线。理解其版本脉络、配置语义与底层实现后,你既能在日常项目中开箱即用地生成 JSON Schema,也能在遇到能力边界时通过 override、definitions 与errorMode组合出符合业务要求的精确输出。
- 后端
- 前端
【免费下载链接】valibot
The modular and type safe schema library for validating structural data 🤖
相关推荐
Harbor 版本演进全览:从 v0.1.0 到 v1.8.0 的关键功能里程碑解析
Harbor 版本演进全览:从 v0.1.0 到 v1.8.0 的关键功能里程碑解析 Harbor 是一款开源的云原生制品仓库,用于内容的存储、签名与安全扫描。
后端云原生镜像仓库Trigger.dev schema-to-json 深度解析:多 Schema 校验库到 JSON Schema 的统一转换与版本演进
Trigger.dev schema to json 深度解析:多 Schema 校验库到 JSON Schema 的统一转换与版本演进 @trigger.de
AI Agent后端任务调度开发工具可观测性AI 应用ok-ww 鸣潮自动化完整指南:后台战斗、一键日常、刷声骸
ok ww 鸣潮自动化完整指南:后台战斗、一键日常、刷声骸 ok ww 是一款《鸣潮》开源自动化工具,基于图像识别,通过模拟键鼠输入完成一键日常、后台自动战斗、
GUI 自动化计算机视觉RPA人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考