JSON Schema Draft 7 / 2019-09 / 2020-12 差异迁移

· 约 3 分钟 Schema 校验

JSON Schema 每隔几年出新 Draft,语法和默认行为会微调。线上服务正在校验成百上千条记录时,盲目升级会出两类问题:老 schema 在新 validator 下语义变了新 schema 用老关键字被拒绝。下面按时间顺序讲清哪些地方变了。

版本时间线

版本发布关键词变化
Draft 42013required 从 boolean 改成数组(对,以前是 boolean)
Draft 62017exclusiveMinimum / exclusiveMaximum 从 boolean 改成数字
Draft 72018加入 if/then/else 条件校验、readOnly / writeOnly
2019-092019$defs 取代 definitions$ref 允许和其他关键字共存;拆分 dependencies
2020-122020数组校验语法重构(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" }
}

元组改用 prefixItemsitems 统一表示”剩余所有元素”。

迁移风险:如果沿用老语法而 validator 升级到 2020-12,数组校验会静默失败——本该校验为元组的数组被当成 items: [...](单个 schema 的数组),一般会被拒绝但错误信息晦涩。

变化五:unevaluatedProperties / unevaluatedItems(2019-09+)

新关键字,配合 allOf / oneOf 使用,用于拒绝”所有子 schema 都没覆盖到”的字段:

{
  "allOf": [
    { "properties": { "a": {} } },
    { "properties": { "b": {} } }
  ],
  "unevaluatedProperties": false
}

这个 schema 拒绝除 ab 外的任何字段。Draft 7 里没有等价物——additionalProperties: falseallOf 场景下不起作用。

变化六:format 默认不再校验

2019-09 起,format 默认变成注释而非约束——"format": "email" 不会自动校验邮箱格式,除非 validator 配置启用。

// Ajv 示例
new Ajv2020({ validateFormats: true })  // 必须显式启用

风险:老代码默认是校验的,升级后”邮箱格式错”不再报错,业务验证失效。回归测试必须覆盖 format 校验。

迁移 checklist

按顺序做:

  1. 升级 validator 库,Draft 保持不变——观察警告
  2. 打开 strict 模式,跑全量测试,记录 deprecated 关键字
  3. 批量改 definitions$defs
  4. dependenciesdependentRequired / dependentSchemas
  5. 数组元组 schema 改 prefixItems
  6. 需要的地方加 unevaluatedProperties
  7. 最后改 $schema 声明到新版本

不要跳过第 2 步直接改 $schema——validator 会突然按新语义校验,老 schema 很多地方会失效。

OpenAPI 与 JSON Schema 的版本对应

OpenAPIJSON Schema Draft
2.0 (Swagger)Draft 4 子集 + 自定义扩展
3.0.xWright-00 子集 + 自定义(nullablediscriminator
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 一键切换,迁移时来回切看行为差异最直观。

❓ 常见问题

我的项目应该用哪个 Draft?

新项目直接上 2020-12——工具链已普遍支持,OpenAPI 3.1 也对齐了这个版本。存量项目如果 OpenAPI 3.0 或 Ajv 6,保留 Draft 7 即可,稳定工具链完善。2019-09 是过渡版本,没必要专门用。

为什么 OpenAPI 3.0 不能直接用 Draft 7?

OpenAPI 3.0 使用的是 JSON Schema Draft Wright-00 的子集(2017 年的草案),自行定义了 nullablediscriminator 等扩展,不完全兼容 Draft 7。OpenAPI 3.1 起改用完整的 Draft 2020-12,两者终于能复用 schema。

$ref 旁边能不能写别的关键字?

Draft 7 及以前规范上不允许——$ref 出现后,其他关键字会被忽略。但很多实现容忍。2019-09 起正式允许 $ref 与其他关键字共存,并明确了合并语义。迁移到新版可以把之前放 allOf 里绕开的关键字直接并回去。

definitions 还能用吗?

Draft 7 用 definitions,2019-09 起推荐改用 $defs。旧 definitions 仍被识别(兼容保留),但新写的 schema 一律写 $defs。同样,dependencies 拆成了 dependentRequireddependentSchemas,新版别再用老关键字。

打开 Schema 校验 JSON Schema 校验 · 实时错误定位 · Draft 7/2019/2020