JSON Schema: What It Is and How to Use It
Learn JSON Schema from scratch. Understand types, constraints, composition keywords, $ref, and see a complete practical example for API validation.
什么是 JSON Schema?
JSON Schema 是一种词汇,允许您描述 JSON 数据的结构。可以将其视为一种合同:它定义了 JSON 文档应具有的字段、这些字段必须是什么类型,以及它们必须满足的约束。当文档符合其模式时,它是 有效的。当不符合时,验证器会告诉您具体出了什么问题。
这很重要,因为 JSON 本身在结构上是宽松的。 JSON 规范 中没有任何内容阻止 API 返回一个字符串而您期望的是一个数字,或者省略一个必需字段。JSON Schema 解决了这个问题。
为什么 JSON Schema 重要
- API 合同。 精确地定义您的 API 期望和返回的内容。客户端和服务器团队可以独立验证有效负载。
- 数据验证。 在边界捕获格式错误的数据——在表单提交、Webhook 有效负载、配置文件或数据库写入中。
- 文档。 模式是机器可读的文档。工具可以从中生成可读的文档、表单,甚至模拟数据。
- 代码生成。 直接从您的模式生成 TypeScript 接口、Go 结构或 Python 数据类。
基础知识:类型、属性、必需
每个 JSON Schema 都以 type 开始。这告诉验证器在顶层预期什么类型的值:
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "用户的全名"
},
"age": {
"type": "integer",
"description": "年龄(以年为单位)"
}
},
"required": ["name"]
}
该模式表示:“我期望一个 JSON 对象,具有一个 name 属性(字符串,必需)和一个可选的 age 属性(整数)。”任何缺少 name 或提供非字符串 name 的对象都是无效的。
所有 JSON Schema 类型
JSON Schema 支持七种基本类型。以下是每种类型及其最小模式示例:
| 类型 | 有效值 | 模式示例 |
|---|---|---|
string | "hello","" | {"type": "string"} |
number | 3.14,-1,0 | {"type": "number"} |
integer | 42,-7 | {"type": "integer"} |
boolean | true,false | {"type": "boolean"} |
null | null | {"type": "null"} |
object | {"key": "value"} | {"type": "object", "properties": {...}} |
array | [1, 2, 3] | {"type": "array", "items": {...}} |
对象详细信息
使用 properties 定义预期字段,使用 required 列出必需字段,使用 additionalProperties 控制是否允许额外字段:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "email"],
"additionalProperties": false
}
将 additionalProperties: false 设置为拒绝任何包含未在 properties 中列出的键的对象。这对于严格的 API 合同非常有用。
数组详细信息
使用 items 定义每个元素应是什么样子,使用 minItems / maxItems 限制长度:
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
这接受一个包含 1 到 10 个唯一字符串的数组。空数组或包含重复项的数组将无法通过验证。
约束和验证关键字
除了基本类型,JSON Schema 还为每种类型提供了细粒度的约束:
字符串约束
| 关键字 | 描述 | 示例 |
|---|---|---|
minLength | 最小字符数 | "minLength": 1 |
maxLength | 最大字符数 | "maxLength": 255 |
pattern | 字符串必须匹配的正则表达式 | "pattern": "^[A-Z]{2}\\\\d{4}$" |
format | 语义格式提示 | "format": "email" |
enum | 允许的值 | "enum": ["active", "inactive"] |
数字约束
| 关键字 | 描述 | 示例 |
|---|---|---|
minimum | 值必须 >= 这个 | "minimum": 0 |
maximum | 值必须 <= 这个 | "maximum": 100 |
exclusiveMinimum | 值必须 > 这个 | "exclusiveMinimum": 0 |
exclusiveMaximum | 值必须 < 这个 | "exclusiveMaximum": 1000 |
multipleOf | 值必须能被这个整除 | "multipleOf": 0.01 |
组合模式:allOf、anyOf、oneOf、not
组合关键字允许您从简单模式构建复杂模式:
- allOf — 数据必须符合 所有 列出的模式。用于组合多个约束或扩展基本模式。
- anyOf — 数据必须符合 至少一个 列出的模式。适用于接受多种格式的字段。
- oneOf — 数据必须符合 恰好一个 列出的模式。适用于区分联合。
- not — 数据必须 不 符合给定的模式。
{
"oneOf": [
{
"type": "object",
"properties": {
"type": { "const": "email" },
"address": { "type": "string", "format": "email" }
},
"required": ["type", "address"]
},
{
"type": "object",
"properties": {
"type": { "const": "phone" },
"number": { "type": "string", "pattern": "^\\\\+?[0-9]{7,15}$" }
},
"required": ["type", "number"]
}
]
}
该模式接受电子邮件联系人或电话联系人,但不能同时接受——这是一种干净的建模标记联合的方式。
使用 $ref 的可重用性
模式很快会变得重复。$ref 关键字允许您引用可重用的定义:
{
"type": "object",
"properties": {
"billing_address": { "$ref": "#/$defs/address" },
"shipping_address": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^[0-9]{5}(-[0-9]{4})?$" }
},
"required": ["street", "city", "zip"]
}
}
}
billing_address 和 shipping_address 共享相同的结构而无需重复。$defs 部分(在旧草案中称为 definitions)是存储可重用模式的常规位置。
完整示例:用户注册验证
以下是一个用于验证用户注册有效负载的真实模式。它使用了我们所涵盖的大多数功能:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "用户注册",
"description": "POST /api/register 端点的模式",
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30,
"pattern": "^[a-zA-Z0-9_]+$",
"description": "字母数字用户名,3-30 个字符"
},
"email": {
"type": "string",
"format": "email",
"maxLength": 254
},
"password": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"description": "至少 8 个字符"
},
"age": {
"type": "integer",
"minimum": 13,
"maximum": 150
},
"role": {
"type": "string",
"enum": ["user", "moderator"],
"default": "user"
},
"acceptedTerms": {
"type": "boolean",
"const": true
},
"tags": {
"type": "array",
"items": { "type": "string", "maxLength": 20 },
"maxItems": 5,
"uniqueItems": true
}
},
"required": ["username", "email", "password", "acceptedTerms"],
"additionalProperties": false
}
请注意,每个字段都有明确的、可强制执行的约束。验证器将拒绝 2 个字符的用户名、低于 13 岁的年龄、重复的标签或任何未在模式中列出的额外字段。
尝试一下: 粘贴任何 JSON 并使用我们的 JSON Schema Generator 自动生成模式。
JSON Schema 在现实世界中的应用
OpenAPI / Swagger
OpenAPI 使用 JSON Schema(带有一些扩展)来定义请求体、响应形状和查询参数。OpenAPI 规范中的每个 schema 块都是一个 JSON Schema。如果您编写 OpenAPI 文档,您已经在编写 JSON Schema。
表单验证
像 react-jsonschema-form 和 ajv(JavaScript 中最快的 JSON Schema 验证器)这样的库使用模式在运行时生成和验证表单。只需定义一次模式,后端和前端都可以根据相同的规则进行验证。
数据库模式
MongoDB 在集合级别支持 JSON Schema 验证。您可以设置一个 $jsonSchema 验证器,拒绝任何不符合您的模式的文档的插入或更新。
配置文件
VS Code、ESLint 和许多其他工具使用 JSON Schema 验证其配置文件。当您编辑 tsconfig.json 时获得的自动补全?它是由 JSON Schema 提供支持的。
草案版本比较
JSON Schema 经过多个草案的演变。以下是您将遇到的草案:
| 草案 | 年份 | 主要新增内容 | 状态 |
|---|---|---|---|
| Draft-04 | 2013 | 核心词汇,$ref,基本类型 | 过时(仍广泛使用) |
| Draft-06 | 2017 | const,contains,propertyNames | 过时 |
| Draft-07 | 2018 | if/then/else,readOnly,writeOnly | 广泛支持 |
| 2019-09 | 2019 | $defs,unevaluatedProperties,dependentRequired | 主要验证器支持 |
| 2020-12 | 2020 | prefixItems(替代元组验证),动态 $ref | 当前 / 推荐 |
对于新项目,请使用 2020-12。如果您需要与现有工具的最大兼容性,Draft-07 是安全的选择——它几乎具有普遍的库支持。
常见错误及避免方法
- 忘记
required。 在properties下定义的属性默认是可选的。如果字段必须存在,请在required中列出它。 - 混淆
number和integer。number接受小数;integer不接受。对于 ID、计数和其他整数值,请使用integer。 - 过早过度约束。 从捕获真实错误的最小模式开始。您可以随时收紧约束——放宽它们是一个破坏性更改。
- 不使用
$ref。 在多个地方重复相同的子模式是维护噩梦。将共享结构提取到$defs中。 - 忽视
format验证。 默认情况下,大多数验证器将format视为注释,而不是约束。您需要显式启用格式验证(例如,ajv.addFormat()或传递{ validateFormats: true })。
尝试一下: 使用我们的 JSON Schema Generator 从任何 JSON 示例生成模式,然后自定义约束以匹配您的要求。