☰
Valibot to JSON Schema 转换器演进全解析:从 v0.1.0 到 v1.8.0 的功能地图、配置体系与实现原理
2026/9/25 4:12:44 网站建设 项目流程
  • 后端
  • 前端

【免费下载链接】valibot

The modular and type safe schema library for validating structural data 🤖

项目地址:https://gitcode.com/gh_mirrors/va/valibot
点击查看免费下载

@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.12024-09 ~ 2024-09初版发布、递归 schema 修复、titleaction、Deno 类型导出修复
正式版v1.0.02025-03errorMode取代force、exactOptional/undefinedable、首批字符串格式 action
定义与定制v1.1.0 ~ v1.3.02025-05 ~ 2025-06definitions、ignoreActions、typeMode、override 三件套、toJsonSchemaDefs、全局定义
现代规格v1.4.0 ~ v1.5.02025-12draft-2020-12 与 OpenAPI 3.0、toStandardJsonSchema、自定义JsonSchema类型
边界补齐v1.6.0 ~ v1.8.02026-03 ~ 2026-09never、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'
definitionsRecord<string, GenericSchema>用于构造递归 schema 的定义;不传则自动生成
overrideSchema(context: OverrideSchemaContext) => JsonSchema \| null \| undefined覆盖某个 Valibot schema 的 JSON Schema 转换
ignoreActionsstring[]转换时忽略指定 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):

  1. 初始化ConversionContext:definitions空对象、referenceMap(ReferenceMap实例)、getterMap;
  2. 从config.definitions或getGlobalDefs()取定义,把每个定义的 schema 注册进referenceMap并预转换进context.definitions;
  3. 调用convertSchema转换目标 schema;
  4. 按target写入$schema;
  5. 若存在引用,写入$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 🤖

项目地址:https://gitcode.com/gh_mirrors/va/valibot
点击查看免费下载

相关推荐

上一篇:5分钟快速指南:如何在Blender中完美导入Rhino 3D模型文件
下一篇:终极Blender插件指南:无缝导入Rhino 3D模型的完整解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询