Valibot 两个 multipleOf 导出后只剩最后一个
to-json-schema 1.8.0 会覆盖前一个 multipleOf 约束,使导出 schema 接受运行期拒绝的数字。用独立交叉分支保留两项要求,并核对接受范围。
字段在 Valibot 中要求既是2的倍数,也是3的倍数,但导出 JSON Schema 后,3居然能通过验证。将两个 multipleOf 调换顺序,错误通过的数字又变了,应检查转换时是否只保留了最后一项约束。
在 Valibot 1.5.0 与 to-json-schema 1.8.0 的组合中,下面的管道运行期会拒绝3:
const schema = v.pipe(v.number(), v.multipleOf(2), v.multipleOf(3));
转换器却逐项给同一个 jsonSchema.multipleOf 赋值,最终只剩 multipleOf: 3。输入3满足导出的规则,但不满足原来的整组要求。
需要同时满足的规则不能靠交换顺序保留
把最后一项换成2,只会让2的倍数通过,依然丢掉另一条条件。两个正整数例子可以合并成 multipleOf(6),但不能将任意小数要求直接相乘;那可能改变允许的集合,也会遇到数值表示与范围问题。
对于这里两个独立的数值约束,可将它们写成明确的交叉分支:
import * as v from 'valibot';
import { toJsonSchema } from '@valibot/to-json-schema';
const schema = v.intersect([
v.pipe(v.number(), v.multipleOf(2)),
v.pipe(v.number(), v.multipleOf(3)),
]);
const jsonSchema = toJsonSchema(schema);
1.8.0会将 intersect 转成 allOf,每个分支独立保留自己的 multipleOf。输入必须满足所有分支。这个例子没有 transform、默认值或复杂输出合并;已有 schema 含这些行为时,需要核对运行期 intersect 的结果,不能直接把整条管道机械拆开。
修改 schema 可能同时影响运行期的错误路径和错误数量。对外API若依赖原来的错误格式,应一起检查,不要只比较生成文档里的一个字段。
用反例同时校验运行期和导出结果
至少比较2、3、6、负倍数和不合法类型。对这个正整数要求,2和3都应被拒绝,6应被接受;按业务是否允许0和负数,再明确叠加范围条件,而不是让 multipleOf 隐含承担这些限制。
若转换结果本来已经带 allOf,手工补充时不能覆盖原数组。批量修复还需识别嵌套管道和重复约束;上述局部重写不是自动发现全部受影响字段的工具。
这与运行期对很大数字或小数余数的容差是另一件事。比如Zod 大安全整数 multipleOf 误判涉及浮点判断本身;本题即使使用小整数,仍会因导出丢条件而出错。
正式to-json-schema 1.8.0仍逐次覆盖该字段,没有已核证的正式自动修复。升级或撤回临时处理后,都应重新比较同一组接受与拒绝样例;生成过程没有报错,并不能证明两种校验器的语义一致。


