DEBUG.WIKI
雨云;给项目一台云服务器
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关

Valibot 校验通过,转成 JSON Schema 后却被 oneOf 拒绝

共享 discriminator 的 variant 允许多个候选分支匹配,oneOf 却要求恰好一个。限定转换到已确认的 schema,避免全局改写所有联合约束。

同一个对象在 Valibot 中校验通过,导出的 JSON Schema 交给 Ajv 却失败,错误指向 oneOf。若 variant 里有两个选项使用相同 discriminator 值,先比较“至少匹配一个”和“恰好匹配一个”的区别。

例如狗的两种记录格式分别要求 name 或 breed,输入同时带着这两个字段:

import * as v from 'valibot';

const Pet = v.variant('type', [
  v.object({ type: v.literal('dog'), name: v.string() }),
  v.object({ type: v.literal('dog'), breed: v.string() }),
  v.object({ type: v.literal('cat'), indoor: v.boolean() }),
]);

{ type: 'dog', name: 'Rex', breed: 'Beagle' } 能满足两个 dog 选项。Valibot 1.5.0 的 variant 会尝试 discriminator 匹配的候选,一旦找到有效结果便接受。to-json-schema 1.8.0 却把所有 variant 直接写成 oneOf,两个候选都有效反而造成失败。

先确认确实允许分支重叠

如果业务要求同一种 type 只能有唯一结构,应重新审查数据契约;不能为了通过转换而擅自放宽它。这里处理的是有意允许重叠、运行期定义也接受它的 schema。

对上面这一个已经确认的 Pet,可以只在转换时使用 anyOf:

import { toJsonSchema } from '@valibot/to-json-schema';

const jsonSchema = toJsonSchema(Pet, {
  overrideSchema: ({ valibotSchema, jsonSchema }) => {
    if (valibotSchema === Pet && jsonSchema.oneOf) {
      const { oneOf, ...rest } = jsonSchema;
      return { ...rest, anyOf: oneOf };
    }
  },
});

这段代码接在 Pet 定义之后。它保留其他生成字段,移除 oneOf,再加入 anyOf;若把两者一起留下,原来的 oneOf 仍然会拒绝重叠输入。运行期 Pet 本身没有被替换。

overrideSchema 是转换器的扩展入口,不是自动检测重叠的通用算法。嵌套 variant、引用或其他已声明的 schema 需要各自核对;不要递归地把全部 oneOf 都换成 anyOf。某些协议本来就依赖“恰好一个”来拒绝歧义数据。

比较接受范围,不只看输出里有没有 anyOf

覆盖只带 name、只带 breed、两者都有、两者都缺、未知 type 以及字段类型错误。对这里没有转换动作的简单对象结构,运行期是否接受与导出 schema 是否接受应保持一致。

若原 schema 还有默认值、字段剔除或 transform,JSON Schema 的校验结果并不自动等同于 Valibot 输出对象。先区分输入是否合法和转换后的数据是什么,再安排迁移。

已核对的正式 to-json-schema 1.8.0 仍对 variant 无条件输出 oneOf,不能因有临时覆盖方案就宣称库已自动修复。后续采用正式修正版时,要撤回相应局部覆盖并复查同一组样例,避免两套修正长期叠加。