JSON Schema 每隔几年出新 Draft,语法和默认行为会微调。线上服务正在校验成百上千条记录时,盲目升级会出两类问题:老 schema 在新 validator 下语义变了,新 schema 用老关键字被拒绝。下面按时间顺序讲清哪些地方变了。
版本时间线
| 版本 | 发布 | 关键词变化 |
|---|---|---|
| Draft 4 | 2013 | required 从 boolean 改成数组(对,以前是 boolean) |
| Draft 6 | 2017 | exclusiveMinimum / exclusiveMaximum 从 boolean 改成数字 |
| Draft 7 | 2018 | 加入 if/then/else 条件校验、readOnly / writeOnly |
| 2019-09 | 2019 | $defs 取代 definitions;$ref 允许和其他关键字共存;拆分 dependencies |
| 2020-12 | 2020 | 数组校验语法重构(items 含义变了) |
变化一:$ref 不再”霸占” sibling
Draft 7 及以前:
{
"$ref": "#/definitions/User",
"description": "用户对象" ← 这里的 description 会被忽略
}
2019-09 起:
{
"$ref": "#/$defs/User",
"description": "用户对象" ← 现在这个 description 生效
}
迁移风险:如果你之前用 allOf: [ { $ref: ... }, { description: ... } ] 绕开这个限制,升级后可以直接并回去。但不要一次改动两处——先升级 validator,跑完回归再合并 allOf。
变化二:definitions → $defs
// Draft 7
{
"definitions": {
"User": { ... }
},
"$ref": "#/definitions/User"
}
// 2019-09+
{
"$defs": {
"User": { ... }
},
"$ref": "#/$defs/User"
}
原因:definitions 被占用于别的 Schema 方言(OpenAPI 等),改名避免冲突。
兼容:新版 validator 仍识别 definitions,所以渐进迁移可以双写,逐步切换 $ref 路径。
变化三:dependencies 拆分
Draft 7:
{
"dependencies": {
"credit_card": ["billing_address"], ← 这是 required
"password": { "minLength": 8 } ← 这是 schema
}
}
一个关键字干两件事,语义靠 value 类型推断——模糊。
2019-09 起拆成两个:
{
"dependentRequired": {
"credit_card": ["billing_address"]
},
"dependentSchemas": {
"password": { "minLength": 8 }
}
}
迁移:两种写法各自对应。注意部分 validator 对老语法会提示 deprecated,但不会报错。
变化四:数组 items 语义变了(2020-12)
这是最容易踩坑的变化。
Draft 7 及 2019-09:
{
"type": "array",
"items": [
{ "type": "string" },
{ "type": "number" }
],
"additionalItems": { "type": "boolean" }
}
items 是数组时表示”前 N 个元素的类型”(元组),additionalItems 管剩余的。
2020-12:
{
"type": "array",
"prefixItems": [
{ "type": "string" },
{ "type": "number" }
],
"items": { "type": "boolean" }
}
元组改用 prefixItems,items 统一表示”剩余所有元素”。
迁移风险:如果沿用老语法而 validator 升级到 2020-12,数组校验会静默失败——本该校验为元组的数组被当成 items: [...](单个 schema 的数组),一般会被拒绝但错误信息晦涩。
变化五:unevaluatedProperties / unevaluatedItems(2019-09+)
新关键字,配合 allOf / oneOf 使用,用于拒绝”所有子 schema 都没覆盖到”的字段:
{
"allOf": [
{ "properties": { "a": {} } },
{ "properties": { "b": {} } }
],
"unevaluatedProperties": false
}
这个 schema 拒绝除 a、b 外的任何字段。Draft 7 里没有等价物——additionalProperties: false 在 allOf 场景下不起作用。
变化六:format 默认不再校验
2019-09 起,format 默认变成注释而非约束——"format": "email" 不会自动校验邮箱格式,除非 validator 配置启用。
// Ajv 示例
new Ajv2020({ validateFormats: true }) // 必须显式启用
风险:老代码默认是校验的,升级后”邮箱格式错”不再报错,业务验证失效。回归测试必须覆盖 format 校验。
迁移 checklist
按顺序做:
- 升级 validator 库,Draft 保持不变——观察警告
- 打开 strict 模式,跑全量测试,记录 deprecated 关键字
- 批量改
definitions→$defs - 拆
dependencies→dependentRequired/dependentSchemas - 数组元组 schema 改
prefixItems - 需要的地方加
unevaluatedProperties - 最后改
$schema声明到新版本
不要跳过第 2 步直接改 $schema——validator 会突然按新语义校验,老 schema 很多地方会失效。
OpenAPI 与 JSON Schema 的版本对应
| OpenAPI | JSON Schema Draft |
|---|---|
| 2.0 (Swagger) | Draft 4 子集 + 自定义扩展 |
| 3.0.x | Wright-00 子集 + 自定义(nullable、discriminator) |
| 3.1.x | 完整的 2020-12 |
OpenAPI 3.1 是对齐点——升级到这个版本就能和 JSON Schema 生态复用。
选哪个 Draft
- 全新项目 / OpenAPI 3.1:2020-12
- Ajv 6 / 老 OpenAPI 3.0 项目:Draft 7 留着
- 过渡期 (2019-09):不建议专门用,特性差不多直接跳到 2020-12
本地校验工具
贴 schema、贴样本 JSON,实时标出哪个字段不合规、报错信息精确到 JSONPath。支持 Draft 7 / 2019-09 / 2020-12 一键切换,迁移时来回切看行为差异最直观。