JSON Schema 基础:从语法到实战

JSON Schema 是什么?说白了,它是一套描述 JSON 数据结构的规则语言。你给出一份 JSON 数据,Schema 就能告诉你:这数据长对了没?

我在团队推行 API 治理时,第一件事就是让大家把接口的请求和响应都配上 Schema。为什么?因为口头约定太容易出错了——前端说「我传个 userId」,后端以为是个字符串,结果是个数字,线上直接崩。有了 Schema,这些破事能少一半。

核心认知: JSON Schema 不是数据格式,而是数据的「说明书」。它定义字段类型、取值范围、必填可选,甚至字段之间的依赖关系。

4.1 JSON Schema 的语法骨架

一个最简的 Schema 长这样:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/user.schema.json",
  "title": "User",
  "description": "用户基础信息",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "required": ["name"]
}

几个关键字段我解释一下:

  • $schema:声明你用的是哪个版本的 Schema 规范。我个人习惯用 draft-07,生态最成熟。
  • $id:这个 Schema 的唯一标识,类似身份证号。跨文件引用时全靠它。
  • title / description:给人看的,不是给机器看的。但别小看它——我见过一个项目,接口文档和 Schema 描述对不上,排查了三天才发现是描述写错了。
  • type:根节点的数据类型。API 响应体通常是 object。
  • properties:对象里的字段定义。
  • required:必填字段列表。注意,required 是个数组,不是布尔值。
我的习惯: 每个 Schema 都写 description,哪怕只有一句话。半年后回头看,你会感谢自己。

4.2 关键字详解:你一定会用到的那些

JSON Schema 的关键字很多,但日常高频使用的其实就十几个。我按用途分了三类:

类别 关键字 作用 示例
类型约束 type 指定数据类型 "type": "string"
类型约束 enum 限定取值列表 "enum": ["dev", "test", "prod"]
数值校验 minimum / maximum 数值范围下限/上限 "minimum": 0, "maximum": 100
数值校验 exclusiveMinimum / exclusiveMaximum 是否包含边界值 "exclusiveMinimum": true
字符串校验 minLength / maxLength 字符串长度范围 "minLength": 1, "maxLength": 64
字符串校验 pattern 正则表达式匹配 "pattern": "^[a-zA-Z0-9]+$"
数组校验 minItems / maxItems 数组元素个数范围 "minItems": 1, "maxItems": 10
数组校验 uniqueItems 元素是否必须唯一 "uniqueItems": true
对象校验 required 必填字段列表 "required": ["id", "name"]
对象校验 additionalProperties 是否允许额外字段 "additionalProperties": false

这里有个坑我要重点说——additionalProperties。默认情况下它是 true,意味着你的对象可以塞进任何没定义的字段。我曾经接手过一个老项目,接口返回里莫名其妙多了一个字段,前端没处理,页面直接白屏。后来我强制所有 Schema 都加上 "additionalProperties": false,这类问题再没出现过。

4.3 验证规则:数据到底合不合格?

验证规则是 Schema 的核心能力。我把它拆成四个维度:

4.3.1 类型验证

JSON Schema 支持六种基础类型:string、number、integer、boolean、array、object。还有一个 null,但我不建议你在 API 里用 null——它会让前端处理变得很麻烦。用 "type": ["string", "null"] 这种联合类型,不如直接约定缺省值。

4.3.2 格式验证

除了基础类型,Schema 还内置了一些格式校验:

{
  "type": "string",
  "format": "email"      // 邮箱格式
  // 还有:date-time, uri, ipv4, uuid 等
}

注意,format 的校验强度取决于你用的验证器。有些库只做格式检查,不做语义验证。比如 "format": "email",它只检查有没有 @ 符号,不会真的去发邮件验证。

4.3.3 组合验证

这是 Schema 最强大的地方。你可以用逻辑关键字组合多个规则:

{
  "allOf": [
    { "type": "object" },
    { "required": ["id"] }
  ],
  "anyOf": [
    { "properties": { "status": { "enum": ["active"] } } },
    { "properties": { "status": { "enum": ["inactive"] } } }
  ],
  "oneOf": [
    { "required": ["email"] },
    { "required": ["phone"] }
  ]
}
  • allOf:所有子规则必须同时满足。相当于 AND。
  • anyOf:至少满足一个子规则。相当于 OR。
  • oneOf:恰好满足一个子规则。相当于 XOR。

我在设计用户注册接口时用过 oneOf:用户可以用邮箱注册,也可以用手机号注册,但不能两个都填。这个场景用 oneOf 表达得非常清晰。

4.3.4 条件验证

if-then-else 结构让 Schema 有了「逻辑分支」的能力:

{
  "type": "object",
  "properties": {
    "type": { "enum": ["personal", "company"] },
    "taxId": { "type": "string" }
  },
  "if": {
    "properties": { "type": { "const": "company" } }
  },
  "then": {
    "required": ["taxId"]
  },
  "else": {
    "not": { "required": ["taxId"] }
  }
}

这个例子什么意思?如果 type 是 company,那么 taxId 必填;否则 taxId 不能出现。你想想看,如果没有条件验证,你得在后端写多少 if-else?

我曾经踩过的坑: 条件验证中的 const 关键字,在 draft-07 里才正式支持。如果你用 draft-04,const 是不存在的,得用 enum 加单元素数组替代。升级 Schema 版本时一定要检查兼容性。

4.4 完整示例:一个用户管理 API 的 Schema

把上面这些知识点串起来,我们写一个真实场景的 Schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://api.example.com/v1/user/create",
  "title": "UserCreateRequest",
  "description": "创建用户接口的请求体",
  "type": "object",
  "additionalProperties": false,
  "required": ["username", "email", "role"],
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 32,
      "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
      "description": "用户名,字母开头,允许字母数字下划线"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "用户邮箱"
    },
    "role": {
      "type": "string",
      "enum": ["admin", "editor", "viewer"],
      "description": "角色权限"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150,
      "description": "年龄,可选字段"
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "minItems": 0,
      "maxItems": 10,
      "uniqueItems": true,
      "description": "用户标签,最多10个,不可重复"
    }
  }
}

这份 Schema 覆盖了类型、长度、正则、枚举、范围、数组约束,还禁用了额外字段。你拿它去校验数据,基本不会漏掉什么。

4.5 知识体系总览

下面这张图帮你理清 JSON Schema 的核心脉络:

JSON Schema 核心知识体系 JSON Schema 语法骨架 关键字详解 验证规则 组合与条件 $schema / $id type / properties required additionalProperties 类型 / 格式 数值 / 字符串 数组 / 对象 条件 if-then allOf / anyOf / oneOf 💡 核心:语法定义结构,关键字约束字段,验证规则检查数据,组合逻辑处理复杂场景 四者结合,就能覆盖 90% 的 API 数据校验需求

4.6 避坑指南

最后分享几个我实战中遇到的坑:

  • 不要滥用 allOf:allOf 里的子 Schema 如果有冲突,验证会直接失败。我曾经把两个互斥的 required 放进了 allOf,排查了半天才发现是逻辑矛盾。
  • pattern 的性能问题:复杂的正则表达式在数据量大时可能成为瓶颈。我见过一个 Schema 用 .* 开头匹配,结果验证耗时从 2ms 涨到 200ms。能用 minLength 解决的,就别上正则。
  • 版本兼容性:draft-04 和 draft-07 的语法有差异。比如 exclusiveMinimum 在 draft-04 里是布尔值,在 draft-07 里变成了数字。升级时一定要跑回归测试。
  • additionalProperties 的陷阱:它只检查当前对象的额外字段,不会递归检查嵌套对象。如果你要严格限制整个数据结构,每个嵌套对象都得单独设置。