draft-07 和 2020-12 该用哪个
结论:2020-12 是当前推荐版本,`$defs`、`prefixItems`、`unevaluatedProperties` 都在这里;但很多老校验器、代码生成器仍只认 draft-07。选版本的依据不是新旧,而是「链路上每个工具都支持哪个」。

结论:新项目直接用 2020-12,只要校验器、代码生成器或 OpenAPI 工具链中有组件只认 draft-07,就退回 draft-07,并在文件里写死 $schema。
一句话:新项目用 2020-12,链路上有老组件就退 draft-07。
版本演进速览
| 版本 | 年份 | 关键变化 |
|---|---|---|
| draft-04 | 2013 | 早期广泛使用,id 而非 $id |
| draft-06 | 2017 | 加 const、propertyNames |
| draft-07 | 2018 | 加 if/then/else、readOnly、contentMediaType,目前兼容性最好 |
| 2019-09 | 2019 | $defs 取代 definitions,加 unevaluatedProperties |
| 2020-12 | 2020 | prefixItems 取代数组元组写法,$dynamicRef,当前推荐版本 |
2020-12 好用的地方
数组元组写法统一了。 draft-07 里用 items: [{...}, {...}] 表示「第一个元素是 A、第二个是 B」,additionalItems 控制后续;2020-12 改成 prefixItems + items,语义清晰得多,也不会和「所有元素都是同一结构」的写法混淆。
$defs 更规范。 虽然 draft-07 的 definitions 也能用,但 2019-09 之后官方统一为 $defs,新工具默认按这个找。
unevaluatedProperties 解决组合难题。 用 allOf 组合多个 Schema 时,additionalProperties: false 会误杀其他分支定义的字段;unevaluatedProperties: false 只看「最终求值过的属性」,这才是组合场景想要的行为。
什么时候必须退回 draft-07
| 情况 | 原因 |
|---|---|
| 使用 OpenAPI 3.0 | 规范内置的就是 draft-04/07 的子集 |
| 校验器是老版本(ajv 6、部分 Java 库) | 不认识 prefixItems、$dynamicRef |
| 平台方指定 Schema 版本 | 比如某些低代码平台、API 网关 |
| 需要生成代码的工具只支持到 07 | 生成器会静默忽略不认识的关关键字 |
判断方法很直接:拿一段用 prefixItems 的 Schema 丢进你的校验器,如果它不报「未知关键字」而是直接通过校验,说明它在静默忽略——这比报错更危险。
混用时的注意事项
永远写 $schema。 不写时不同库的默认版本不一样,ajv 默认 draft-07,部分 Python 库默认 2020-12。同一个文件在两处校验结果不同,排查起来非常费劲。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.json",
"type": "object"
}
别跨版本引用。 $ref 指向另一个版本的 Schema,行为未定义,多数实现会按当前版本解释,$defs 与 definitions 混着写必然出问题。
升级要成批做。 一个项目里同时存在两种版本的 Schema,等于把「这份文件到底按哪个版本解释」变成人工记忆,早晚出错。
常见问题
如何判断校验器是否真的支持 2020-12?
拿一段使用 prefixItems 的 Schema 丢进校验器:报「未知关键字」说明不支持,直接通过校验则说明它在静默忽略,这比报错更危险。
为什么要让整个项目统一版本?
一个项目同时存在两种版本,等于把「这份文件按哪个版本解释」变成人工记忆,早晚出错。升级应当成批完成。
是否可以不写 $schema?
不建议。不写时不同库的默认版本不一样,ajv 默认 draft-07,部分 Python 库默认 2020-12,同一个文件在两处校验结果可能不同。
相关工具
- Schema 与 TypeScript 类型双向同步:生成器对版本的支持差异
- JSON Schema 从入门到工程化:从基础关键字到组合写法的完整路径