JSON Schema 最佳实践:draft-07 基础与设计取舍
JSON Schema 描述「一份 JSON 该长什么样」,是 API 契约与数据校验的通用语言。本文先讲清 draft-07 的核心关键字,再谈 required、 enum、 format 的取舍,所有示例都能在 JSON Schema 推断 工具里复现。
一、示例 JSON
下面这份用户样例是全文的推断来源,粘贴进站内工具的输入框即得到后续 Schema:
{
"id": 1,
"name": "Ada",
"email": "ada@example.com",
"created_at": "2026-01-01T00:00:00Z",
"active": true,
"role": "admin",
"tags": ["a", "b"],
"address": { "city": "Shanghai", "zip": "200000" }
}二、draft-07 核心关键字
- type:节点的类型。站内推断把整数标为
integer、浮点标为number;两者合并时统一退化为number。 - properties:对象的字段定义,每个键对应一个子 Schema,可任意嵌套。示例中
address会展开成含city/zip的子对象。 - required:字符串数组,列出必须有值的字段。默认把样例里出现的属性全部写入。
- items:数组元素的 Schema。空数组无法推断元素类型,会输出空 schema
{},请至少给一条元素样例。 - enum:取值白名单,写在
items上时表示数组每个元素都从该集合取值。 - format:语义提示,如
date、date-time、time、email、uuid、uri。
和 TypeScript interface 的区别:TS 只在编译期约束代码,JSON Schema 是运行期契约,可校验任意来源的 JSON (跨语言、跨进程);TS 无法表达 enum 之外的取值白名单与 format 校验,Schema 可以,并且能反过来生成示例数据。
三、required 的取舍:安全 vs 演进
required 是「契约强度」。全部标记时,任何缺字段的报文都会校验失败,能及早暴露上游问题;代价是接口稍作演进(新增必填字段前的过渡期)就会误报。 一个都不标,规则就形同虚设。推荐做法:
- 先用推断的默认结果(全标)作为「理想契约」参考;
- 再按真实接口约定把真正可选、可缺省的字段从 required 移除;
- 多份样例合并时,站内推断对 required 取交集,只有每条样例都出现的字段才会保留,天然筛掉偶发字段。
四、enum 与 format 的取舍
- enum 有阈值。站内推断只在数组项为 2–8 个互不相同的原始值、且类型单一时标注 enum。 少于 2 个没区分度,多于 8 个则把契约绑成「闭集」,新增取值都要改 Schema,此时应退回普通 type。
- format 会「看走眼」。它基于形状匹配,长得像邮箱的字符串就会命中。站内推断在合并多份样例时,只有两侧 format 完全一致才保留;不一致就丢成普通 string,避免把偶发形状固化成约束。
- 日期会二次校验。仅形状像日期还不够,站内推断会再用真实日历校验(例如
2026-13-40不会标成 date),减少误报。
五、anyOf / oneOf:结构冲突怎么表达
当同一字段在不同样例里类型不同(一会儿字符串、一会儿对象),无法合并成单一 Schema,站内推断会折叠为 anyOf。设计建议:
- 「满足其一即可」→ anyOf;「恰好满足其一」→ oneOf(互斥取值,如多态消息)。
- 作为页面里「响应形态不确定」的表达,anyOf 比硬塞一个模糊的 string 更诚实,但下游代码需分支处理,能用统一结构就别用组合关键字。
- 数组多样例合并时,同名字段递归合并、required 取交集、类型冲突向上折叠为 anyOf。
六、从 Schema 回到数据
契约写好后要能自证。站内 Schema → JSON 示例 工具按 draft-07 语义生成符合约束的数据:const / enum / default / examples 字面量优先采用,email、date-time 等 format 生成语义化占位值,可选属性可整体开关。这样「推断 → 复核 → 反推数据」就形成了闭环。
七、常见错误
- 把单份样例的 required 当真理。样例里出现的字段未必必填,务必结合接口约定复核。
- 空数组期望推断出元素类型。空数组只能给出 items: {},补一条元素样例即可。
- 用 enum 表达自由文本。取值超过 8 个且会增长时不应使用 enum,否则每加一个取值都要改契约。
- 把 format 当强校验。多数校验器默认只对 format 做提示而非硬失败,真正强约束还需在业务层补正则或规则。
- draft 版本混用。站内推断输出 draft-07;若要升级到 2020-12,注意
definitions改为$defs等差异。
动手跑一遍
粘贴本文示例 JSON,停顿即可得到 draft-07 Schema。切换 required / format / enum 开关看差异,再用反推工具生成示例数据。全部在本地完成,数据不上传。
常见问题
单份样例推断出来的 required 可信吗?
结构层面(类型、嵌套、数组项)可信;required 属于归纳,需要复核。站内推断把样例里出现的属性默认全部写入 required,单份样例无法证明某字段「一定存在」。如果接口允许字段缺省,请手工从 required 数组里移除,或直接关闭「标记 required」开关。
enum 什么时候自动识别,什么时候不该用?
站内推断的规则是:数组项全部为原始值(字符串 / 数字 / 布尔 / null)、互不相同且数量在 2–8 个之间、且合并后类型单一,才会在 items 上标注 enum。少于 2 个没有区分度,超过 8 个基本退化为自由取值,这时用 enum 反而把契约绑死,应该改用普通 type 加文档说明。
format 会不会误标?合并时怎么处理?
format 是「形状识别」,例如 [^\s@]+@[^\s@]+\.[^\s@]+ 会被标为 email,但恰好长成邮箱的普通字符串也会命中。站内推断在合并多个样例时,只有当两侧 format 完全一致才保留,否则退化为普通 string,避免把偶发形状固化成约束。
anyOf 和 oneOf 该怎么选?
需要「满足其中之一即可」用 anyOf,多个分支允许同时成立;需要「恰好满足其中一个」用 oneOf,适用于互斥取值(如不同类型的消息)。站内推断在结构不兼容(如某个字段一会儿是字符串、一会儿是对象)时会折叠为 anyOf,因为无法证明分支互斥。
这些结论在站内怎么验证?
打开 JSON Schema 推断工具,粘贴示例后停顿约 400ms 即得到 draft-07 Schema。逐一切换「标记 required」「识别格式」「识别枚举」开关,可以实时对比差异。反向可用「Schema → JSON 示例」把同一份 Schema 还原成数据。