← 全部文章

什么是 JSON Schema?一份带示例的实用指南

JSON Schema 是一套描述 JSON 数据结构与约束的词汇。学习核心关键字、看真实示例,并在 JavaScript、Python 与浏览器中校验 JSON。

JSON Schema 是一套用来描述 JSON 文档结构、约束与类型的词汇。它让你能够准确定义什么样的 JSON 才算合法 —— 哪些键必须存在、每个值必须是什么类型、允许哪些取值 —— 然后用这些规则自动校验任意 JSON 文档。本指南讲解 JSON Schema 是什么、如何编写,以及如何在 JavaScript、Python 和浏览器中用它来校验 JSON。

什么是 JSON Schema?

JSON Schema 本身就是一个 JSON 文档。它描述另一份 JSON 文档,就像数据库 schema 描述一张表一样:它声明数据应当具备的形状、类型与约束。JSON Schema 告诉校验器 —— 类库、API、表单生成器、IDE —— 对某段特定 JSON 而言「合法」意味着什么。

规范由 json-schema.org 维护,目前是 Draft 2020-12。它被 OpenAPI(用于描述 REST API 的标准)、JSON Forms(由 schema 生成 UI)、IDE 工具(VS Code 用 JSON Schema 为 package.jsontsconfig.json 提供自动补全)以及数不清的内部数据管道所使用。

一个最小的 JSON Schema 示例

下面是一份代表用户的 JSON 文档,以及描述其期望结构的 schema:

// 被校验的 JSON 文档
{
  "id": 42,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 30,
  "active": true
}
// JSON Schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "name", "email"],
  "properties": {
    "id":     { "type": "integer" },
    "name":   { "type": "string", "minLength": 1 },
    "email":  { "type": "string", "format": "email" },
    "age":    { "type": "integer", "minimum": 0, "maximum": 150 },
    "active": { "type": "boolean" }
  },
  "additionalProperties": false
}

这份 schema 表达的是:文档必须是对象;idnameemail 是必需字段;age 若存在必须在 0 到 150 之间;并且除了列出的五个键外不允许任何其他键。

核心关键字

type

指定值的 JSON 类型。合法的类型有 "string""number""integer""boolean""array""object""null"。也可以用数组同时允许多种类型:

{ "type": ["string", "null"] }  // 值可以是字符串或 null

properties

定义对象中每个键的 schema。每个条目本身也是一份 schema:

{
  "type": "object",
  "properties": {
    "firstName": { "type": "string" },
    "lastName":  { "type": "string" },
    "age":       { "type": "integer", "minimum": 0 }
  }
}

required

对象中必须出现的键名数组。出现在 properties 中但不在 required 中的键是可选的 —— 可以缺失,但若存在就必须符合其 schema。

items

定义数组中每个元素的 schema。下面这份 schema 要求数组元素均为字符串:

{
  "type": "array",
  "items": { "type": "string" }
}

字符串约束

{
  "type": "string",
  "minLength": 1,
  "maxLength": 100,
  "pattern": "^[a-zA-Z0-9_]+
quot; // 正则表达式 }

数字约束

{
  "type": "number",
  "minimum": 0,
  "maximum": 1,
  "multipleOf": 0.01   // 必须是 0.01 的倍数
}

enum

把值限定在一组固定的允许取值之中:

{ "enum": ["pending", "active", "suspended", "deleted"] }

additionalProperties

控制是否允许出现 properties 中未列出的键。设为 false 可拒绝任何意外的键 —— 适合严格的 API 契约:

{ "additionalProperties": false }

一个真实世界的 JSON Schema 示例

下面是一个电商 API 中商品对象的 schema —— 你在 OpenAPI 定义或内部数据契约中会看到的那种:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/product.json",
  "title": "Product",
  "description": "A product available for purchase",
  "type": "object",
  "required": ["id", "name", "price", "category"],
  "properties": {
    "id": {
      "type": "integer",
      "description": "Unique product identifier"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "price": {
      "type": "number",
      "minimum": 0,
      "description": "Price in USD"
    },
    "category": {
      "type": "string",
      "enum": ["electronics", "clothing", "books", "home", "sports"]
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "uniqueItems": true
    },
    "dimensions": {
      "type": "object",
      "properties": {
        "width":  { "type": "number", "minimum": 0 },
        "height": { "type": "number", "minimum": 0 },
        "depth":  { "type": "number", "minimum": 0 }
      },
      "required": ["width", "height", "depth"]
    },
    "inStock": { "type": "boolean" }
  },
  "additionalProperties": false
}

如何用 schema 校验 JSON

在挑选 jsonschema 校验器?标准选择是 Ajv(JavaScript)、jsonschema(Python),以及 jsonschemavalidator.net 这类在线服务。如果你需要相反的方向 —— 由示例生成 json schema,或者由一份 json schema 从 json 文档反推 —— 可在 Python 中试试 GenSON,或在浏览器中试试 transform.tools。这些是 json schema generator 工具,从样例推断出一个起步用的 schema。事后手动收紧 requiredadditionalProperties 和取值约束即可。

在 JavaScript 中(Ajv)

Ajv 是 JavaScript 生态中使用最广泛的 JSON Schema 校验器。它支持 Draft-07 和 Draft 2020-12。

npm install ajv
import Ajv from 'ajv';

const ajv = new Ajv();

const schema = {
  type: 'object',
  required: ['id', 'name', 'email'],
  properties: {
    id:    { type: 'integer' },
    name:  { type: 'string', minLength: 1 },
    email: { type: 'string' },
  },
  additionalProperties: false,
};

const validate = ajv.compile(schema);

const data = { id: 42, name: 'Alice', email: 'alice@example.com' };
const valid = validate(data);

if (!valid) {
  console.error(validate.errors);
} else {
  console.log('Valid!');
}

ajv.compile(schema) 返回一个可复用的校验函数。编译一次然后反复调用,比每次都重新编译显著快得多。

在 JavaScript 中(浏览器,无依赖)

对于简单的类型与必填字段检查,可以手写校验而不引入依赖 —— 但只要超出这种琐碎检查,就请使用 Ajv。schema 的校验逻辑足够复杂,自己手写很容易出错。

在 Python 中(jsonschema)

pip install jsonschema
import json
import jsonschema
from jsonschema import validate, ValidationError

schema = {
    "type": "object",
    "required": ["id", "name", "email"],
    "properties": {
        "id":    {"type": "integer"},
        "name":  {"type": "string", "minLength": 1},
        "email": {"type": "string"},
    },
    "additionalProperties": False,
}

data = {"id": 42, "name": "Alice", "email": "alice@example.com"}

try:
    validate(instance=data, schema=schema)
    print("Valid!")
except ValidationError as e:
    print(f"Invalid: {e.message}")

用 schema 校验一份 JSON 文件

import json
import jsonschema

with open('schema.json') as f:
    schema = json.load(f)

with open('data.json') as f:
    data = json.load(f)

jsonschema.validate(instance=data, schema=schema)

在浏览器中(fixjson)

如果你想在动手处理之前确认一份 JSON 文档在结构上合法 —— 而又不想写代码 —— fixjson 的 JSON 校验器 会即时检查语法并标记错误。粘贴你的 JSON,任何语法问题都会在内联中显示出错的精确行号和位置。

若要进行完整的 schema 校验(检查类型、必填字段和各种约束),请使用上面任一基于类库的方案,或类似 jsonschemavalidator.net 的专用 schema 测试工具。

$ref:复用 schema

真实项目里不会把所有 schema 都内联定义。$ref 关键字让你通过 $id 或文档内的 JSON Pointer 路径引用另一份 schema,使 schema 保持 DRY 与可组合。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "address": {
      "type": "object",
      "required": ["street", "city", "country"],
      "properties": {
        "street":  { "type": "string" },
        "city":    { "type": "string" },
        "country": { "type": "string", "minLength": 2, "maxLength": 2 }
      }
    }
  },
  "type": "object",
  "properties": {
    "billingAddress":  { "$ref": "#/$defs/address" },
    "shippingAddress": { "$ref": "#/$defs/address" }
  }
}

$defs 是在同一文件内定义可复用子 schema 的标准位置(它在 Draft 2019-09 中取代了 definitions)。

组合 schema:allOf、anyOf、oneOf

JSON Schema 提供了三个组合关键字,用于表达「必须全部满足」「至少满足其一」「正好满足其一」:

// allOf: 必须满足列出的每一份 schema
{
  "allOf": [
    { "type": "object" },
    { "required": ["id"] },
    { "properties": { "id": { "type": "integer" } } }
  ]
}

// anyOf: 必须至少满足其中一份 schema
{
  "anyOf": [
    { "type": "string" },
    { "type": "null" }
  ]
}

// oneOf: 必须正好满足其中一份 schema(各份之间必须互斥)
{
  "oneOf": [
    { "properties": { "type": { "const": "circle" } }, "required": ["radius"] },
    { "properties": { "type": { "const": "rectangle" } }, "required": ["width", "height"] }
  ]
}

format 关键字:date-time、email、uri、uuid

除原始类型外,JSON Schema 的 format 关键字还能为字符串标注其期望的形态 —— ISO 日期时间、邮箱地址、URI、UUID、IP 地址等等。形如:

{
  "type": "object",
  "properties": {
    "id":         { "type": "string", "format": "uuid" },
    "created_at": { "type": "string", "format": "date-time" },
    "email":      { "type": "string", "format": "email" },
    "homepage":   { "type": "string", "format": "uri" }
  }
}

一个微妙但重要的点:在 Draft 2020-12(以及 2019-09) 中,format 关键字默认是一个 注释(annotation) —— 除非校验器明确启用,否则它不会强制检查格式。在 Draft-07 及更早版本中,有些校验器默认把它当作断言。要在 Ajv 中将其变为严格:

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv();
addFormats(ajv);              // 注册 date-time、email、uri、uuid 等

// 在 2020-12 中,你必须在 meta-schema 中开启 format 断言,或者根据
// Ajv 版本传入 { validateFormats: true }。

常见的 format 取值:date-timedatetimedurationemailidn-emailhostnameipv4ipv6uriuri-referenceuuidregexjson-pointer

format 当作提示而非安全边界 —— 一个能通过 format: "email" 的字符串仍然是不可信输入。要验证是否真的可投递或合法,请在带外验证(DNS、发送确认邮件、跑你自己控制的正则)。

常见的 JSON Schema 误用

把 "properties" 和 "required" 搞混

把键写进 properties 并不会让它变成必填。properties 中的键只是定义了「如果它出现」时应当遵守的 schema。要强制其必须出现,还必须把它列入 required

想表达「数字」却写了「integer」

"type": "integer" 会拒绝 3.14。如果你的字段可以是任意数字(包括小数),请使用 "type": "number"

忘记 "additionalProperties" 默认是 true

默认情况下,JSON Schema 允许任何未在 properties 中列出的额外属性。这是有意的 —— schema 默认是开放的,遵循 Postel 法则。如果你想要一个封闭对象,请显式设置 "additionalProperties": false

不指定 $schema

不同 draft 的语义不同。省略 $schema 关键字会让校验器去猜要用哪个版本。请始终包含它:

{ "$schema": "https://json-schema.org/draft/2020-12/schema" }

把 JSON Schema 校验当作安全校验

JSON Schema 校验结构与类型 —— 它不是一种数据清洗工具。一个通过了 "type": "string" 的字符串仍然可能包含 SQL 注入、XSS payload 或其他恶意内容。Schema 校验是一道前置闸门,而不是完整的安全防护。

JSON Schema 与 OpenAPI

OpenAPI(旧称 Swagger)使用 JSON Schema 的一个子集来描述请求体、响应体和参数。如果你写过 OpenAPI 规范,那你也就写过 JSON Schema —— 不过 OpenAPI 用的是带些扩展与限制的修改方言。

这意味着 JSON Schema 的知识可以直接迁移到 API 设计中。OpenAPI 文档的 components/schemas 部分就是一组 JSON Schema,Stoplight、Redoc 与 Swagger UI 等工具会自动把它们渲染成交互式文档。

在实践中使用 Ajv

有两个开关在任何真实项目里都值回票价:

  • strict: true(在现代 Ajv 中是默认) —— 在 schema 编译期拒绝未知关键字与未知 format,从而在数据被悄无声息地放过之前,捕获诸如 "minimun" 这样的拼写错误或漏写的 $schema
  • allErrors: true —— 收集每一次校验失败,而不是在第一次错误时就停止。和 ajv.errorsText() 搭配生成面向用户的错误消息也很好用。

要按 schema 生成 JSON 作为测试与 fixture,可使用 json-schema-faker:它会遍历 schema 并生成匹配的值(类型、范围、枚举,搭配 ajv-formats 时连 format 也能符合)。适合用于基于属性的测试或从 schema 给 UI 填充数据。

从现有 JSON 生成 JSON Schema

如果你已有 JSON 数据,想从中生成一份起步用的 schema,有不少工具可以自动化:

  • quicktype.io —— 从样例 JSON 推断 schema(以及 TypeScript、Go、Python 等的类型定义)
  • generate-schema(npm) —— generate-schema json schema.json data.json
  • Python:genson —— pip install genson,然后 SchemaBuilder 可以从一个或多个样例对象推断出 schema

生成的 schema 只是起点,不是成品。它能从样例数据推断类型,却不了解你的业务规则 —— 它没法推断出某个字符串字段是邮箱、某个整数必须为正,或者两个字段彼此互斥。在投入生产之前,请始终对生成的 schema 进行审查与收紧。

常见问题

JSON Schema 是用来干什么的?

描述并校验 JSON 数据的结构 —— 哪些键必须存在、每个值必须是什么类型、允许哪些取值。它是 OpenAPI、package.json/tsconfig.json 的 IDE 自动补全、表单生成以及数据管道契约背后的核心。

JSON 和 JSON Schema 有什么区别?

JSON 是数据;JSON Schema 是另一份用来描述「合法数据该长什么样」的 JSON 文档。如果你对 JSON 本身不熟,可先从 什么是 JSON? 开始。

我应该用哪个 JSON Schema draft?

Draft 2020-12 是当前版本,对新 schema 推荐使用;Draft-07 由于工具支持广泛仍然常见。务必用 $schema 声明版本,免得校验器去猜。

怎样用 schema 校验 JSON?

在 JavaScript 用 Ajv,在 Python 用 jsonschema 库(上面有示例),或者用网页版校验器。要先确认 JSON 本身在语法上合法,可参见 如何校验 JSON,或直接通过 校验器 跑一遍。

JSON Schema 会校验 format(email、date-time、uri…)吗?

在 Draft 2020-12 / 2019-09 中,format 关键字默认只是注释 —— 校验器只在被配置时才会强制检查。在 Ajv 中,注册 ajv-formats 并启用 format 断言,才会真正拒绝格式不合的值。

结语

JSON Schema 把非正式的文档(「这个字段应该是数字」)变成了一份机器可校验的契约,各种语言、团队和系统中的校验器都能据此一致地执行。核心词汇 —— typepropertiesrequireditemsenum —— 涵盖了现实中绝大多数用例。$ref、组合关键字与 format 等更高级特性,则可应对其余情形。

请从一份只强制你真正需要强制的最小 schema 开始,等对数据的理解更扎实后再逐步增加约束。会拒绝合法数据的 schema 比没有 schema 还糟。

如果你要校验的 JSON 在到达 schema 校验器之前就有语法错误,先把这些修好 —— schema 校验器要求输入是语法合法的 JSON。JSON 干净之后,再 校验它,然后再应用你的 schema。