HandyTools Hub

← 全部指南

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 关键字接受:objectarraystringnumberintegerbooleannull。不匹配声明类型的值直接校验失败。

{ "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_iduserId 这类笔误,而不是让它们漏到下游。

字符串与数字

字符串有长度和 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 再引用它,别在同一节点混用 $refproperties

Schema 到底是干什么的

校验只是起点。一份好的 schema 是唯一事实源,能支撑:

  • 接口请求/响应校验——在边界用精确的报错消息拒绝坏载荷。
  • 生成表单——schema 驱动的表单渲染器直接从契约构建字段、类型和必填性,表单与接口永不漂移。
  • 测试数据生成——把 schema 喂给生成器,得到契约每个分支上真实、合法的夹具。
  • 文档——schema 本身就是你的数据结构 API 规范。

同一份文档服务这四件事,所以把 schema 写严格,收益远不止校验。

快速参考

  • schema 是一份约束 JSON 文档;$schema 声明方言,type 声明外层形状。
  • 类型:objectarraystringnumberintegerbooleannull;可空用 ["string","null"]
  • 对象:properties(逐字段 schema)、required(必填列表)、additionalProperties: false(严格)。
  • 字符串:minLengthmaxLengthpattern(完整匹配)。数字:minimumexclusiveMinimummaximummultipleOf
  • 数组:items(逐元素)或数组形式(元组);minItemsmaxItemsuniqueItems
  • 分支:oneOf(恰好一个)、anyOf(至少一个)、allOf(全部)、$ref$defs 复用形状。
  • Draft-07 的 $ref 会覆盖同节点兄弟关键字——用 $defs 引用,别混用。
  • 一份 schema 从唯一事实源驱动校验、生成表单、测试数据与文档。

不想手写 schema,JSON Schema Generator 能从样本推导形状或可视化构建,Test Data Generator 把 schema 变成真实夹具,JSON Formatter 在你设计契约时保持源数据可读。Schema 与常见 JSON 错误YAML 的坑同属一类话题——三者都是把宽松的文本变成可靠的数据。