从一个恼人的线上事故说起
前后端联调时最常踩的坑:后端认为 price 一定是数字,前端传入 "128.5"(字符串)或漏传 userId,直到运行时才爆出 500。这类问题靠人眼 review 拦不住,靠直觉也难测全——正确姿势是用 JSON Schema 把『这里必须是 number、这个必填、数组至少 1 个』提前声明成契约。
本篇文章带你从校验学起,再引申到类型生成。
一个最小 Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "total"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"total": { "type": "number", "minimum": 0 },
"items": { "type": "array", "minItems": 1,
"items": { "type": "object",
"properties": {
"sku": { "type": "string" },
"price": { "type": "number", "minimum": 0 }
}, "required": ["sku", "price"] } },
"status": { "enum": ["paid", "pending", "cancelled"] }
}
}
这份 Schema 表达了四条关键的『硬规则』:必填 id/total、total ≥ 0、items 非空数组且每个元素都含 sku+price、status 只能取三个值之一。校验器按它跑一遍,问题当场暴露。
核心关键字速查
| 关键字 | 用途 |
|---|---|
type |
object/array/string/number/integer/boolean/null |
required |
必填字段数组 |
properties |
对象字段各自的 Schema |
items / prefixItems |
数组元素 Schema |
enum |
允许的取值集合 |
const |
固定值(判别字段) |
allOf/anyOf/oneOf |
组合多份子 Schema |
$ref |
引用已定义 Schema(复用) |
format / pattern |
格式与正则约束 |
用严格性「收紧」生成的 Schema
从样本 JSON 自动生成(json-to-jsonschema)得到的是宽松推断,适合起步但不具备约束力。要让它真正可校验,人工加三件事:
- 显式
required:把确实必填的字段列全; - 值域约束:字符串补
enum/format(date-time、email),数字补minimum/maximum,数组补minItems/maxItems; - 统一入口
$ref + $defs:把可复用对象抽成$defs,多处$ref引用,避免 Schema 膨胀重复。
oneOf 实现类型判别
联调中常遇到『同一结构体按字段值不同、形状不同』的事件体,例如 notification 里 type 区分 email/sms:
"oneOf": [
{ "properties": { "type": { "const": "email" }, "to": { "type": "string" } }, "required": ["to"] },
{ "properties": { "type": { "const": "sms" }, "phone": { "type": "string" } }, "required": ["phone"] }
]
oneOf 恰好命中一个,校验器能靠 const 判别字段精确匹配。
从 Schema 生成强类型
一旦 Schema 成为唯一事实源,剩下的就是「翻译成各语言类型」:
JSON 样例 ──json-to-jsonschema──▶ Schema ──schema-to-typescript──▶ interface 定义
──schema-to-go──▶ struct 定义
- 从 JSON → 类型:快,但与结构约束脱节,样本缺字段就漏类型;
- 从 Schema → 类型:保留 enum/required/nullable 语义,产出更可靠,且校验与类型同源。
把整套接到你手头的 JSON 工具站里:格式化 → 生成 Schema → 收紧 → 生成 DTO,一次成型。
落地清单
- [ ] 为每个接口定义一份 Schema,保存为版本化文件;
- [ ] 用
json-to-jsonschema起步,再人工补 required/constraints; - [ ] 运行时校验(后端或 API 网关挂上校验器)与类型生成同源;
- [ ] 判别联合用
oneOf + const,公共对象用$ref复用; - [ ] 契约定好后,用生成器产出 TypeScript/Go DTO 替代手写。
自查
拿本地一个真实接口的 JSON 响应,走一遍「格式化 → 生成 Schema → 补 required → 生成 TS 类型」的完整流程,确认最后生成的类型能让你在编译器里直接发现「漏传字段」的隐患——这一步走通,JSON Schema 的价值你就抓住了。