JSON Schema 验证指南:验证你的数据结构
每个从外部来源接收数据的应用程序都面临同一个根本问题:我能信任这些数据吗?无论是 API 请求体、配置文件、队列消息还是数据导入,不可信的数据在进入系统之前都必须经过验证。JSON Schema 提供了一种标准化的、与语言无关的方式来定义有效数据的样子,并验证传入数据是否符合该定义。本指南涵盖了从编写第一个 Schema 到复杂验证的高级模式的所有内容,并提供了可以立即应用的实用示例。
什么是 JSON Schema?
JSON Schema 是一个描述其他 JSON 文档结构和约束的 JSON 文档。它是一种允许你注解和验证 JSON 数据的词汇表,由互联网工程任务组(IETF)标准化。JSON Schema 定义了关于必须存在哪些字段、它们必须是什么类型、哪些值是可接受的以及对象和数组应该如何结构的规则。
把 JSON Schema 想象成你的数据契约。就像数据库 Schema 定义了表的列、类型和约束一样,JSON Schema 定义了 JSON 文档的属性、类型和约束。任何满足 Schema 中所有约束的 JSON 数据被称为有效实例,而违反任何约束的数据则是无效的。
JSON Schema 不是什么
- 不是数据格式:JSON Schema 不定义数据如何序列化或传输。它只描述有效数据的样子。
- 不是业务逻辑的替代品:Schema 验证检查结构正确性(类型、格式、范围)。复杂的业务规则(如「用户不能转账超过其余额」)属于应用程序代码。
- 不是数据库 Schema:虽然概念相似,但 JSON Schema 验证的是 JSON 文档,而不是数据库表。不过,你可以将 JSON Schema 与数据库约束结合使用,实现纵深防御。
JSON Schema 版本
JSON Schema 经历了多个草案的演进,每个草案都添加了功能并完善了词汇表。了解这些版本有助于你为项目选择合适的版本并避免兼容性问题。
| 版本 | $schema URI | 状态 | 主要功能 |
|---|---|---|---|
| Draft 2020-12 | https://json-schema.org/draft/2020-12/schema | 当前版本 | prefixItems, dynamicRef, 词汇表支持 |
| Draft 2019-09 | https://json-schema.org/draft/2019-09/schema | 稳定版 | unevaluatedProperties, $recursiveRef |
| Draft 7 | http://json-schema.org/draft-07/schema# | 广泛支持 | if/then/else, contentEncoding |
| Draft 6 | http://json-schema.org/draft-06/schema# | 旧版 | propertyNames, contains |
| Draft 4 | http://json-schema.org/draft-04/schema# | 已弃用 | 最初广泛采用的版本 |
编写你的第一个 Schema
让我们从一个简单的例子开始:一个包含姓名、邮箱和年龄的用户对象的 Schema。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"description": "A user account in the system",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100,
"description": "The user's full name"
},
"email": {
"type": "string",
"format": "email",
"description": "The user's email address"
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150,
"description": "The user's age in years"
}
},
"required": ["name", "email"],
"additionalProperties": false
}此 Schema 声明了一个有效用户必须是一个对象,其中 name 和 email 是必需的字符串属性。age 属性是可选的,但如果存在则必须是 0 到 150 之间的整数。additionalProperties: false 约束阻止了任何未在 Schema 中定义的属性,这可以捕获拼写错误和意外字段。
核心关键字参考
JSON Schema 提供了丰富的关键字词汇表来定义约束。以下按类别组织的最重要的关键字。
类型关键字
| 关键字 | 描述 | 示例 |
|---|---|---|
| type | 预期数据类型 | "type": "string" 或 "type": ["string", "null"] |
| enum | 允许的值列表 | "enum": ["active", "inactive", "pending"] |
| const | 必须等于此精确值 | "const": "v2" |
数字约束
| 关键字 | 描述 | 示例 |
|---|---|---|
| minimum | 最小值(包含) | "minimum": 0 |
| exclusiveMinimum | 最小值(不包含) | "exclusiveMinimum": 0 |
| maximum | 最大值(包含) | "maximum": 100 |
| exclusiveMaximum | 最大值(不包含) | "exclusiveMaximum": 100 |
| multipleOf | 必须为此值的倍数 | "multipleOf": 0.01 |
字符串约束
| 关键字 | 描述 | 示例 |
|---|---|---|
| minLength | 最小字符串长度 | "minLength": 1 |
| maxLength | 最大字符串长度 | "maxLength": 255 |
| pattern | 字符串必须匹配的正则表达式 | "pattern": "^[A-Z]{2}\\d{4}$" |
| format | 语义格式(email、uri、date-time 等) | "format": "email" |
对象约束
| 关键字 | 描述 | 示例 |
|---|---|---|
| properties | 每个已知属性的 Schema | "properties": {"name": {"type": "string"}} |
| required | 必需属性列表 | "required": ["name", "email"] |
| additionalProperties | 是否允许额外属性 | "additionalProperties": false |
| minProperties | 最小属性数量 | "minProperties": 1 |
| maxProperties | 最大属性数量 | "maxProperties": 10 |
| patternProperties | 匹配正则表达式的属性 Schema | "patternProperties": {"^S_": {"type": "string"}} |
数组约束
| 关键字 | 描述 | 示例 |
|---|---|---|
| items | 所有数组项的 Schema | "items": {"type": "string"} |
| prefixItems | 位置项的 Schema(Draft 2020-12) | "prefixItems": [{"type": "string"}, {"type": "number"}] |
| minItems | 最小项数 | "minItems": 1 |
| maxItems | 最大项数 | "maxItems": 100 |
| uniqueItems | 所有项必须唯一 | "uniqueItems": true |
常见 Schema 模式
现实世界中的 Schema 通常需要超越简单类型检查的模式。以下是最常用的模式。
使用 if/then/else 进行条件验证
使用条件逻辑根据属性值应用不同的约束。例如,支付对象根据支付方式需要不同的字段。
{
"type": "object",
"properties": {
"method": { "enum": ["credit_card", "bank_transfer"] },
"card_number": { "type": "string" },
"routing_number": { "type": "string" }
},
"required": ["method"],
"if": {
"properties": { "method": { "const": "credit_card" } }
},
"then": {
"required": ["card_number"]
},
"else": {
"required": ["routing_number"]
}
}使用 allOf、anyOf、oneOf 进行组合
组合关键字允许你以强大的方式组合 Schema:
- allOf:数据必须满足所有子 Schema。用于组合多个约束或混入可复用的 Schema 片段。
- anyOf:数据必须至少满足一个子 Schema。用于多种形状都可接受的联合类型。
- oneOf:数据必须恰好满足一个子 Schema。用于互斥的备选项。
{
"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": "^\\+?[1-9]\\d{1,14}$" }
},
"required": ["type", "number"]
}
]
}使用 $ref 复用 Schema
$ref 关键字允许你引用和复用 Schema,消除重复并保持 Schema 的可维护性。你可以引用同一文档内或外部文件中的 Schema。
{
"$id": "https://example.com/schemas/order.json",
"type": "object",
"properties": {
"customer": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" },
"billing": { "$ref": "#/$defs/address" },
"items": {
"type": "array",
"items": { "$ref": "#/$defs/lineItem" },
"minItems": 1
}
},
"required": ["customer", "items"],
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}(-\\d{4})?$" },
"country": { "type": "string", "minLength": 2, "maxLength": 2 }
},
"required": ["street", "city", "zip", "country"]
},
"lineItem": {
"type": "object",
"properties": {
"product": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"price": { "type": "number", "exclusiveMinimum": 0 }
},
"required": ["product", "quantity", "price"]
}
}
}可空类型
在 JSON Schema Draft 2020-12 中,可空类型使用包含 "null" 的类型数组来表示:
{
"type": ["string", "null"],
"description": "An optional display name, or null if not set"
}在较早的草案中,OpenAPI 规范使用了 nullable: true 关键字。对于标准 JSON Schema,请始终使用类型数组方法。
格式验证
format 关键字提供了超越结构检查的语义验证。它指定字符串必须符合一种众所周知的格式。常用支持的格式包括:
| 格式 | 描述 | 示例 |
|---|---|---|
| 电子邮件地址 | user@example.com | |
| uri | 有效的 URI | https://example.com/path |
| uri-reference | URI 或相对引用 | /path/to/resource |
| date-time | ISO 8601 日期时间 | 2026-05-19T14:30:00Z |
| date | ISO 8601 日期 | 2026-05-19 |
| time | ISO 8601 时间 | 14:30:00Z |
| ipv4 | IPv4 地址 | 192.168.1.1 |
| ipv6 | IPv6 地址 | ::1 |
| uuid | 通用唯一标识符 | 550e8400-e29b-41d4-a716-446655440000 |
| hostname | 互联网主机名 | www.example.com |
以编程方式验证 JSON 数据
Schema 验证在集成到应用程序代码中时最为有用。以下是使用不同语言中流行库的示例。
JavaScript 使用 Ajv
Ajv 是 JavaScript 中使用最广泛的 JSON Schema 验证器。它支持所有草案版本,并通过 Schema 的 JIT 编译提供出色的性能。
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
},
required: ['name', 'email'],
additionalProperties: false
};
const validate = ajv.compile(schema);
const valid = validate({ name: 'Alice', email: 'alice@example.com', age: 30 });
if (!valid) {
console.log(validate.errors);
// [{ keyword: 'required', params: { missingProperty: 'email' }, ... }]
}Python 使用 jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name", "email"],
"additionalProperties": False
}
try:
validate(instance={"name": "Alice", "email": "alice@example.com"}, schema=schema)
except ValidationError as e:
print(f"Validation failed: {e.message}")使用 JSON Schema 进行 API 数据验证
JSON Schema 最有价值的应用之一是验证 API 请求和响应数据。这确保了你的 API 接收格式良好的输入并以预期格式返回数据,能够及早捕获错误并向客户端提供清晰的错误消息。
Express.js 中间件
import Ajv from 'ajv';
const ajv = new Ajv({ allErrors: true });
function validateBody(schema) {
const validate = ajv.compile(schema);
return (req, res, next) => {
if (!validate(req.body)) {
return res.status(400).json({
error: 'Validation failed',
details: validate.errors
});
}
next();
};
}
app.post('/api/users',
validateBody({
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
email: { type: 'string', format: 'email' },
role: { enum: ['admin', 'editor', 'viewer'] }
},
required: ['name', 'email'],
additionalProperties: false
}),
(req, res) => {
// req.body is guaranteed valid here
createUser(req.body);
}
);OpenAPI 与 JSON Schema
OpenAPI(前身为 Swagger)使用 JSON Schema 的一个子集来定义 API 请求和响应 Schema。如果你已经在使用 OpenAPI,可以从 API 规范中提取 Schema 并将其用于运行时验证。像 openapi-schema-validator 和 express-openapi-validator 这样的工具可以自动化这个过程,确保你的 API 文档和验证逻辑始终保持同步。
最佳实践
- 始终设置 $schema:包含 $schema 关键字以声明你的 Schema 使用的草案版本。这可以防止歧义并确保验证器正确解释你的 Schema。
- 使用 $id 作为 Schema 标识:$id 关键字为你的 Schema 提供唯一标识符,并作为解析 $ref 引用的基础 URI。即使是本地 Schema,也请始终设置它。
- 设置 additionalProperties: false:默认情况下,JSON Schema 允许对象中有任何额外属性。设置 additionalProperties: false 可以捕获拼写错误和意外字段,使你的 Schema 更严格,API 更可预测。
- 使用 $defs 存放可复用组件:在 $defs 中定义通用 Schema,并使用 $ref 引用它们。这减少了重复并使 Schema 更易于维护。
- 添加描述:每个属性和 Schema 都应有 description 字段。这既作为文档,也帮助其他开发者理解每个字段的用途和约束。
- 在系统边界进行验证:在系统的边缘应用 Schema 验证:API 端点、消息消费者、数据导入管道和配置加载器。不要验证你自己的代码已经产生并信任的数据。
- 在验证器中启用严格模式:配置验证器拒绝未知关键字、强制格式验证并报告所有错误而非在第一个错误处停止。这可以在单次验证中捕获更多问题。
- 为 Schema 版本化:在 $id URI 中包含 Schema 版本(例如 https://example.com/schemas/user/v2.json)。这允许你在不破坏现有消费者的情况下演进 Schema。
- 测试你的 Schema:为你的 Schema 编写包含有效和无效示例的单元测试。这确保你的 Schema 执行你意图的约束,并在 Schema 修改时捕获回归。
- 使用 format 进行语义验证:优先使用 format: "email" 而不是复杂的正则表达式模式。格式更具可读性、更易维护,并受益于验证器库中经过充分测试的验证逻辑。
需要根据 Schema 验证 JSON 数据?试试我们免费的在线 JSON Schema 验证器。粘贴你的 Schema 和数据,即可获得带有详细错误信息的即时验证结果。
JSON Schema 验证器JSON 格式化工具常见问题
什么是 JSON Schema?
JSON Schema 是一个描述其他 JSON 文档结构和约束的 JSON 文档。它让你可以定义必需字段、预期数据类型、值范围、字符串模式和嵌套对象结构。你可以用它来验证传入数据、生成文档和自动创建表单界面。
我应该使用哪个版本的 JSON Schema?
新项目请使用 JSON Schema Draft 2020-12。这是最新的稳定版本,Ajv 等主要验证库都支持它。如果需要与旧工具兼容,Draft 7 也被广泛支持。避免使用 Draft 4 及更早版本,因为它们使用过时的关键字且缺乏现代功能。
JSON Schema 与 TypeScript 接口有什么区别?
TypeScript 接口仅在 TypeScript 代码中提供编译时类型检查。JSON Schema 提供跨编程语言的运行时验证,可以验证来自任何来源(API 请求、文件、数据库)的数据。使用 TypeScript 获得开发时的安全性,使用 JSON Schema 在系统边界进行运行时数据验证。
JSON Schema 可以验证 API 请求体吗?
是的,JSON Schema 被广泛用于 API 请求和响应验证。Express(配合 express-json-validator)、FastAPI 和 Spring Boot 等框架原生支持或通过中间件支持 JSON Schema 验证。根据 Schema 验证请求体可确保传入数据在应用程序处理之前具有正确的结构。
最重要的 JSON Schema 关键字有哪些?
最关键的关键字是:type(数据类型)、properties(对象字段)、required(必需字段)、items(数组元素 Schema)、minimum/maximum(数值边界)、minLength/maxLength(字符串边界)、pattern(字符串正则表达式)、enum(允许的值)和 $ref(复用 Schema 的引用)。这些关键字覆盖了绝大多数验证需求。