JSON模式验证 在发布前捕获API合约违规
JSON模式验证在源头捕获API合约违规。学习核心关键词、Express中的AJV中间件、FastAPI集成、模式漂移检测以及与OpenAPI的关系。
如果您的API返回一个在模式中不存在的字段,或者请求体缺少必需属性,那么您就存在合同违规。这些错误很微妙,通常无声无息,而且在生产环境中很难发现。JSON Schema验证是在问题被引入时更早地捕获它们的层。
本指南介绍了JSON Schema的工作原理,以及在Node.js、Python和AWS中应使用哪些验证器,以及如何保持您的模式与现实保持一致。
什么是JSON Schema
JSON Schema是一种描述JSON文档结构的词汇。它不是代码,而是元数据。您编写一个模式,声明一个有效的JSON对象应该是什么样子,然后运行验证器进行验证。
该模式本身是一个JSON文档。一个最小示例:
{
"$schema": "https://json-schema.org/draft/2020-12",
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["email"]
}
就这样。将此内容传递给任何JSON负载的验证器,您将得到通过或一个结构化的失败列表。
核心关键字
少数几个关键字可以覆盖大多数实际场景中的验证需求:
| 关键字 | 验证内容 | 例子 |
|---|---|---|
type |
值的数据类型 | "type": "string" |
properties |
对象字段的结构 | "properties": { "name": { "type": "string" } } |
required |
哪些属性必须存在 | "required": ["email", "password"] |
additionalProperties |
是否允许未知属性 | "additionalProperties": false |
enum |
值必须是固定集合中的一个 | "enum": ["admin", "editor", "viewer"] |
format |
语义格式检查 | "format": "email" 或 "format": "date-time" |
pattern |
字符串必须匹配正则表达式 | "pattern": "^[a-z0-9-]+$" |
$ref |
引用到另一个模式 | "$ref": "#/$defs/Address" |
additionalProperties: false 值得特别提及。它是使您的模式严格的关键字——任何未在 properties 声明的属性将触发验证错误。它默认是关闭的,这意味着大多数模式在没有启用的情况下会默默接受垃圾字段。
完整模式:用户注册请求
这里是一个完整的用于注册端点请求体的JSON Schema。这是您会编写一次,并在所有接触该端点的层中进行验证的模式类型。
{
"$schema": "https://json-schema.org/draft/2020-12",
"title": "UserRegistration",
"type": "object",
"required": ["email", "password", "username"],
"additionalProperties": false,
"properties": {
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"minLength": 8
},
"username": {
"type": "string",
"pattern": "^[a-zA-Z0-9_]{3,32}$"
},
"role": {
"type": "string",
"enum": ["user", "admin"],
"default": "user"
},
"birthDate": {
"type": "string",
"format": "date"
}
}
}
您可以使用 IO Tools JSON Schema Validator 来交互式测试任何负载,然后再将其集成到您的代码库中。
验证API请求体
Express + AJV
AJV 是Node.js中最快的JSON Schema验证器。以下是一个Express中间件,它在请求到达处理函数之前验证请求体:
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
function validateBody(schema) {
const validate = ajv.compile(schema);
return (req, res, next) => {
if (validate(req.body)) {
return next();
}
res.status(400).json({
error: "Validation failed",
details: validate.errors,
});
};
}
// Usage
app.post("/register", validateBody(registrationSchema), registerHandler);
allErrors: true 告诉AJV收集文档中的所有错误,而不是在第一个错误处停止——当您希望一次性将所有验证失败返回给客户端时非常有用。
FastAPI(Python)
在Python中,FastAPI使用Pydantic作为底层,并自动从您的类型注解生成JSON Schema。如果您处理的是来自外部源的原始JSON Schema而不是Pydantic模型, jsonschema 是标准库:
from jsonschema import validate, ValidationError
schema = { ... } # your schema dict
try:
validate(instance=request_body, schema=schema)
except ValidationError as e:
return {"error": e.message}, 400
AWS API Gateway
API Gateway原生支持请求体验证。您定义一个模型(即一个JSON Schema文档),并将其附加到您的方法作为 REQUEST_BODY 验证器。任何验证失败的请求将在网关级别被拒绝——在您的Lambda函数运行之前。这消除了整个类别的处理错误,并减少了无效流量的冷启动调用。
可重用的模式与 $ref 且 $defs
当多个模式共享一个常见结构——例如地址、货币金额或分页对象时——请在 $defs 中定义一次,并通过 $ref:
{
"$defs": {
"Address": {
"type": "object",
"required": ["street", "city", "country"],
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string", "pattern": "^[A-Z]{2}$" }
}
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/Address" },
"shippingAddress": { "$ref": "#/$defs/Address" }
}
}
引用它。 $ref 对于大型项目,模式存储在单独的文件中,并通过 $id 指向一个URI。支持 addSchema().
模式漂移
当模式和实际API发生分歧时,就会出现模式漂移。这比您想象的更常见:模式只写一次,API不断演进,而没有人更新模式。
症状很微妙。代码中的字段被重命名,但模式中没有更新——验证仍然通过,因为 additionalProperties 没有设置为false。一个必需字段在实践中变为可选,因为代码不再检查它——模式仍然要求它是必需的,但直到客户端发送没有该字段的请求,没有人注意到。
要捕捉漂移,必须将模式验证视为测试,而不是运行时检查。一些团队通过与真实响应固定数据的快照测试来自动化此过程。其他人则在CI中运行模式验证器,以测试一组捕获的API请求。关键点是,模式必须定期使用,而不是只在部署时使用。
JSON Schema 与 OpenAPI
OpenAPI(以前称为Swagger)使用JSON Schema来描述请求和响应体,但有一些修改。早期版本(OpenAPI 2.0、3.0)使用了JSON Schema的子集并添加了扩展。OpenAPI 3.1更贴近于JSON Schema draft 2020-12,因此这些模式基本上是互操作的。
实际区别是:OpenAPI将模式封装在一个更大的文档中,该文档还描述了路径、操作、身份验证和服务器。而单独的JSON Schema只是一个验证合同。如果您正在构建一个以模式为中心的API,您可以为每个端点编写JSON Schema,进行验证,然后将这些模式直接提升到OpenAPI文档中。
从现有数据生成模式
手动编写模式对于新API来说效果很好。对于已有但未文档化的API,从真实负载生成模式通常更快,然后进行优化。
像 genson (Python) 和 generate-schema (Node.js) 可以从样本JSON对象生成一个草案模式。生成的模式通常过于宽松——所有内容都变为可选,没有 additionalProperties: false ——但它为您提供了一个起点。然后您添加 required,收紧 type 定义,以及在值有边界时添加 enum 约束。
这种方法在引入第三方API且没有模式文档时也非常有用。运行几个响应通过模式生成器,合并输出,您就有了一个可用的合同来验证。
JSON Schema验证应存在于每个接受结构化数据的层——HTTP中间件、消息队列消费者、数据库写入路径。合同违规越早被发现,修复成本就越低。一个 在线JSON Schema验证器 允许您在投入任何库集成之前,用真实负载来原型化模式,这使得它成为任何验证工作的第一步。
