一份最小 schema
假设下单接口的响应长这样:
{
"code": 0,
"data": {
"order_id": "20260829001",
"amount": 99.5,
"paid": false
}
}
对应的 schema 可以写成:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["code", "data"],
"properties": {
"code": { "type": "integer" },
"data": {
"type": "object",
"required": ["order_id", "amount", "paid"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" },
"paid": { "type": "boolean" }
},
"additionalProperties": false
}
}
}
additionalProperties: false 表示 data 里多出来的键也算失败。联调早期可以先开着,逼双方把字段写进文档;上线后如果要做兼容,再改成 true 或列出允许的扩展。
常用关键字
| 关键字 | 作用 |
|---|---|
type | object / array / string / number / integer / boolean / null |
properties | 对象有哪些字段 |
required | 哪些字段必须出现 |
items | 数组每个元素的 schema |
enum | 只能是列出的几个值 |
minLength / minimum | 字符串长度、数字下界 |
$ref | 复用另一段定义,避免复制粘贴 |
integer 和 number 不一样:99.5 过不了 integer。金额用 number,或按你们财务约定改成「分」的整数。
数组怎么写
{
"type": "object",
"properties": {
"items": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"required": ["id", "status"],
"properties": {
"id": { "type": "string" },
"status": { "enum": ["pending", "paid", "closed"] }
}
}
}
}
}
空数组 [] 在 minItems 为 0 时合法。如果产品说「至少一条」,把 minItems 改成 1,比在群里口嗨有用。
和「看一眼树」怎么配合
Schema 不会告诉你这次响应里 amount 为什么是 0.01。它只保证是数字。真实值还是要打开 HiJSON 看树,或用 $.data.amount 抽出来。流程可以是:
- 后端或前端先写一份 schema,放进仓库。
- CI 或本地用 AJV、Python
jsonschema跑样例。 - 线上偶发结构不对,把响应贴进 HiJSON,对照 schema 看缺了哪一层。
OpenAPI 的 components.schemas 本质上也是 JSON Schema 的超集。你在 Swagger 里看到的模型,导出后往往能直接当校验输入。
容易写错的地方
- 把
null忘了:字段声明"type": "string",接口却回null。要允许空,写成"type": ["string", "null"]。 - 数字当字符串:
"amount": "99.5"过不了number。先统一类型,再谈校验。 - draft 版本混用:
draft-04和2020-12的items、$ref细节不同。项目里锁一个版本。 - schema 本身也是 JSON:尾逗号、单引号同样会导致 schema 文件解析失败,见 语法错误排查。
什么时候不必上 schema
一次性看一段日志、改一份 mock,用树和 JSONPath 就够。两三个服务要长期对字段,或者开放平台给外部调用,再写 schema 才划算。为了「看起来专业」给内部一次性脚本写 200 行 schema,维护成本会反噬。
常见问题
- HiJSON 会按 schema 校验吗?
- 目前以查看和查询为主。校验可以在本地用 AJV 等库跑;结构对不上时,把实例贴进来对照路径。
- 和 TypeScript 类型是不是一回事?
- 运行时 JSON 不会走 tsc。类型只约束编译期。运行时仍要靠校验或契约测试。两边可以互相生成,但不要假设「有 interface 就不会出现字符串金额」。