JSON Schema 详解:校验、类型,以及真正干活的那些关键字
2026-08-24
JSON 是世界上最宽容的数据格式——而这正是它的弱点。一个接口收 {"user_id": 123},有的调用方发来 {"user_id": "123"} 或 {"userId": 123} 或 {}。当时没人报错,直到后来某个地方崩溃。JSON Schema 就是阻止这种情况的契约。 它是一个 JSON 文档,用来描述另一个 JSON 文档:每个字段必须是什么类型、哪些字段必填、字符串和数字长什么样、数组和嵌套对象如何塑形。本文只讲覆盖 90% 真实校验的那几个关键字。
Schema 本身就是一个 JSON 文档
schema 就是普通 JSON,带一个特殊的 $schema 标记和一个 type:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name"]
}
把它读作一组约束,而不是配方:“值必须是对象;它的 name 必须是字符串且必填;age 若出现则必须是整数。“任何满足约束的数据都能通过校验;多余的数据默认允许,除非你用 additionalProperties 禁止(见下)。
类型系统
type 关键字接受:object、array、string、number、integer、boolean、null。不匹配声明类型的值直接校验失败。
{ "type": "string" } // "hello" ✓ | 42 ✗
{ "type": ["string", "null"] } // 两者皆可
["string", "null"] 这种数组形式是惯用的”可空”——字段要么是 null 要么是字符串。(Draft 2020-12 新增了 type: "null",把 type 数组移到了别处,但数组形式到处仍可用。)
对象:properties、required、additionalProperties
三个关键字定义对象校验:
properties——字段名 → schema 的映射。每个声明的字段值按它的 schema 校验。required——必须存在的字段名数组。默认缺失不等于无效;只有列出的字段是强制的。additionalProperties——对 不在properties里的字段做什么。默认:允许。false禁止它们(严格契约);给一个 schema 则宽松地校验它们。
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"tags": { "type": "array" }
},
"required": ["id"],
"additionalProperties": false
}
additionalProperties: false 是那个严格开关——在边界处就能抓住 user_id 和 userId 这类笔误,而不是让它们漏到下游。
字符串与数字
字符串有长度和 pattern 约束;数字有范围和倍数约束:
{
"type": "string",
"minLength": 3,
"maxLength": 50,
"pattern": "^[a-z0-9-]+$"
}
{
"type": "number",
"minimum": 0,
"exclusiveMinimum": 0,
"maximum": 100,
"multipleOf": 0.5
}
exclusiveMinimum / exclusiveMaximum(draft-04 里是布尔,draft-06 起是独立关键字)表达严格不等式。pattern 是完整的正则匹配,不是”包含”搜索。
数组
数组校验每个元素,外加大小与唯一性:
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
items 用单个 schema 时对每个元素一视同仁。用数组形式(2020-12 的 prefixItems)则是元组:第 0 个元素按 schema 0、第 1 个按 schema 1……这正是坐标对这类定长数据的形状。
组合:oneOf、allOf、anyOf 与 $ref
真实世界的契约需要分支逻辑:
oneOf——恰好一个子 schema 匹配:“要么是邮箱,要么是电话号码。”anyOf——至少一个匹配(做”字符串或字符串数组”这类很有用)。allOf——全部匹配(把基础约束与额外约束组合起来)。$ref——指向同文档别处("$ref": "#/$defs/address")或其他文档的 schema。这是复用形状而不是重复声明的方式:
{
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"required": ["street", "city"],
"additionalProperties": false
}
}
}
一个提醒:draft-07 里 $ref 是排他的(会覆盖同节点的兄弟关键字),所以把共享逻辑放进 $defs 再引用它,别在同一节点混用 $ref 和 properties。
Schema 到底是干什么的
校验只是起点。一份好的 schema 是唯一事实源,能支撑:
- 接口请求/响应校验——在边界用精确的报错消息拒绝坏载荷。
- 生成表单——schema 驱动的表单渲染器直接从契约构建字段、类型和必填性,表单与接口永不漂移。
- 测试数据生成——把 schema 喂给生成器,得到契约每个分支上真实、合法的夹具。
- 文档——schema 本身就是你的数据结构 API 规范。
同一份文档服务这四件事,所以把 schema 写严格,收益远不止校验。
快速参考
- schema 是一份约束 JSON 文档;
$schema声明方言,type声明外层形状。 - 类型:
object、array、string、number、integer、boolean、null;可空用["string","null"]。 - 对象:
properties(逐字段 schema)、required(必填列表)、additionalProperties: false(严格)。 - 字符串:
minLength、maxLength、pattern(完整匹配)。数字:minimum、exclusiveMinimum、maximum、multipleOf。 - 数组:
items(逐元素)或数组形式(元组);minItems、maxItems、uniqueItems。 - 分支:
oneOf(恰好一个)、anyOf(至少一个)、allOf(全部)、$ref从$defs复用形状。 - Draft-07 的
$ref会覆盖同节点兄弟关键字——用$defs引用,别混用。 - 一份 schema 从唯一事实源驱动校验、生成表单、测试数据与文档。
不想手写 schema,JSON Schema Generator 能从样本推导形状或可视化构建,Test Data Generator 把 schema 变成真实夹具,JSON Formatter 在你设计契约时保持源数据可读。Schema 与常见 JSON 错误、YAML 的坑同属一类话题——三者都是把宽松的文本变成可靠的数据。