为什么数据校验是工程刚需
每个后端开发者都遇到过这些场景:
- API 收到客户端请求体,字段缺失、类型错、格式不对,导致后续逻辑崩溃
- 配置文件(如 K8s ConfigMap、CI/CD pipeline)写错字段名,部署时才暴露
- 表单提交的邮箱、URL、日期格式不合法,存入数据库后无法使用
- 微服务间传递的 JSON 消息结构漂移,消费方解析失败
手动写 if/else 校验既繁琐又容易遗漏。JSON Schema 就是为解决这类问题而生的:用一份标准化的 JSON 文档描述数据结构,再用校验器自动验证任意 JSON 是否符合约束。
配套工具:JSON Schema 校验工具
一、JSON Schema 是什么
JSON Schema 本身也是一个 JSON 对象,用一组预定义的关键字(keyword)声明数据必须满足的约束。它由 IETF 标准化,主流版本有:
- draft-07:当前最广泛使用的稳定版本(本工具与本文重点)
- draft-06:引入
const、examples等 - 2019-09 / 2020-12:拆分为多个子规范,新增
prefixItems、unevaluatedProperties等
一份最简单的 Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "name"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"name": { "type": "string", "minLength": 1 }
}
}
$schema 声明所用规范版本,type 限定根类型,required 列出必填属性,properties 定义每个字段的子约束。
二、类型系统:type / enum / const
type
type 可取单值或数组:
{ "type": "string" }
{ "type": ["string", "null"] } // 允许字符串或 null
draft-07 支持 7 种类型:null、boolean、object、array、number、string、integer。注意 integer 是 number 的子集:5 同时满足两者,但 5.1 只满足 number。
enum 与 const
enum 限定值必须在列表中,const 限定值必须严格等于某个值:
{ "enum": ["admin", "editor", "viewer"] }
{ "const": 42 }
enum / const 不限定类型,可跨类型比较。校验器用深度相等判断(对象、数组递归比较)。
三、对象约束
对象是 JSON Schema 中最丰富的约束场景:
{
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"name": { "type": "string", "minLength": 2, "maxLength": 32 },
"email": { "type": "string", "format": "email" }
},
"patternProperties": {
"^x-": { "type": "string" } // 以 x- 开头的键必须为字符串
},
"additionalProperties": false, // 禁止未在 properties 中定义的额外属性
"minProperties": 1,
"maxProperties": 20
}
- required:数组,列出必须存在的键名(不校验值,只校验键存在)
- properties:键名 → 子 schema 的映射,精确匹配键名
- patternProperties:正则 → 子 schema,匹配键名(如
^x-匹配所有自定义扩展头) - additionalProperties:处理未被
properties/patternProperties匹配的额外属性。false表示禁止,schema 表示必须满足该约束,缺省表示放行 - minProperties / maxProperties:限定属性数量
校验顺序通常是:先检查 required,再遍历每个属性,对每个键依次尝试 properties、patternProperties、additionalProperties。
四、数组约束
数组约束分两种模式:
单 schema 模式(列表)
{
"type": "array",
"items": { "type": "string" }, // 所有元素都必须是字符串
"minItems": 1,
"maxItems": 10,
"uniqueItems": true // 元素两两不重复
}
元组模式(Tuple)
{
"type": "array",
"items": [
{ "type": "string" }, // 第 0 位必须是字符串
{ "type": "integer" }, // 第 1 位必须是整数
{ "type": "boolean" } // 第 2 位必须是布尔
],
"additionalItems": false // 禁止超出元组长度的额外元素
}
uniqueItems: true 需要对数组元素做去重判断,实现上常用两两深度比较(O(n²),对常见规模足够),或用规范化字符串作为 Map 键(O(n),但需处理对象键顺序)。
五、字符串约束
{
"type": "string",
"minLength": 6, // 最短 6 个字符
"maxLength": 128, // 最长 128 个字符
"pattern": "^[a-zA-Z0-9_]+$", // 必须匹配正则
"format": "email" // 必须符合 email 格式
}
注意 minLength / maxLength 按 Unicode 码点计数,不是字节也不是 UTF-16 单元。JavaScript 中用 [...str].length 即可正确计数(含 Emoji、中文)。
format 校验
draft-07 定义了一批语义格式,常用 9 种:
| format | 含义 | 示例 |
|---|---|---|
| date-time | RFC 3339 日期时间 | 2026-07-06T12:00:00Z |
| date | YYYY-MM-DD | 2026-07-06 |
| time | HH:MM:SS | 12:00:00 |
| 邮箱 | dev@example.com | |
| uri | 含协议头的 URI | https://example.com |
| ipv4 | IPv4 地址 | 192.168.1.1 |
| ipv6 | IPv6 地址 | ::1 |
| uuid | UUID | 550e8400-e29b-… |
| hostname | 主机名 | example.com |
format 校验默认是提示性的(draft-07 规范允许实现选择是否启用),生产场景通常需要明确开启。本工具的 format 校验为启发式正则实现,覆盖常见用例,但边界用例(如闰秒、非典型时区)可能与 ajv 等严格实现存在差异。
六、数值约束
{
"type": "number",
"minimum": 0, // >= 0
"maximum": 100, // <= 100
"exclusiveMinimum": 0, // > 0(draft-07 起为数值,draft-04 为布尔)
"exclusiveMaximum": 100, // < 100
"multipleOf": 0.5 // 必须是 0.5 的整数倍
}
注意 draft-07 中 exclusiveMinimum / exclusiveMaximum 是数值(如 "exclusiveMinimum": 0 表示严格大于 0),而 draft-04 中它们是与 minimum / maximum 配合使用的布尔修饰符。本工具按 draft-07 语义实现。
multipleOf 实现时需注意浮点精度:0.1 + 0.2 !== 0.3,所以判断 value % multipleOf === 0 不可靠,应用 Math.abs(value / multipleOf - Math.round(value / multipleOf)) < 1e-10 容差判断。
七、组合关键字:allOf / anyOf / oneOf / not
这四个关键字实现逻辑组合,是 Schema 复用的核心:
{
"allOf": [ // 必须同时满足所有子 schema
{ "type": "object" },
{ "required": ["name"] }
],
"anyOf": [ // 至少满足一个子 schema
{ "minimum": 0 },
{ "type": "string" }
],
"oneOf": [ // 恰好满足一个子 schema(不能多也不能少)
{ "type": "string" },
{ "type": "integer" }
],
"not": { "type": "null" } // 必须不满足该子 schema
}
实现要点:
- allOf:对每个子 schema 递归校验,所有错误都收集(不短路)
- anyOf:对每个子 schema 用临时错误列表校验,至少一个为空则通过;否则报「不满足 anyOf」
- oneOf:统计通过的子 schema 数量,必须恰好为 1;多了报「实际匹配 N 个」
- not:子 schema 校验通过则报错,否则通过
oneOf 是最易错的:很多人以为它等价于 anyOf,但 oneOf 要求互斥——如果两个子 schema 都通过,会报错。
八、$ref 引用与 Schema 复用
大型 Schema 会拆分定义并用 $ref 引用,避免重复:
{
"definitions": {
"Address": {
"type": "object",
"properties": {
"city": { "type": "string" },
"zip": { "type": "string" }
},
"required": ["city"]
}
},
"type": "object",
"properties": {
"home": { "$ref": "#/definitions/Address" },
"work": { "$ref": "#/definitions/Address" }
}
}
#/definitions/Address 是 JSON Pointer:# 表示文档根,/definitions/Address 是路径段。路径段中的 ~1 表示 /,~0 表示 ~(JSON Pointer 转义规则)。
循环引用防护
Schema 可能自引用(如树形结构):
{
"definitions": {
"Tree": {
"type": "object",
"properties": {
"value": { "type": "number" },
"children": {
"type": "array",
"items": { "$ref": "#/definitions/Tree" }
}
}
}
}
}
校验器必须维护 $ref 解析栈,遇到栈中已有的引用时跳过,避免无限递归。本工具用 refStack: string[] 实现,每次解析 $ref 前检查是否已在栈中。
本工具的 $ref 限制
为保持轻量,本工具仅支持同文档内部引用(以 #/ 开头),不支持:
- 跨文档外部引用(如
{"$ref": "user.json#/definitions/User"}) $id定义的基准 URI- 动态引用(
$dynamicRef,2020-12 引入)
需要复杂引用的场景建议使用 ajv。
九、轻量校验器实现要点
本工具的校验器(src/utils/jsonSchema.ts,约 380 行)采用递归下降实现,核心思路:
- 入口函数
validate(instance, schema):返回{ valid, errors },errors 是结构化错误列表 - 递归函数
validateNode(instance, schema, path, root, refStack, errors):path是当前实例路径(JSON Pointer 风格),用于错误定位root是 schema 根节点,用于解析$refrefStack是$ref解析栈,防循环errors是共享的错误收集数组
- 关键字分发:按关键字类型(type/enum/数值/字符串/数组/对象/组合)顺序校验
- 类型短路:
type不匹配时直接返回,避免后续关键字对错误类型值校验产生噪音
错误结构:
interface ValidationError {
path: string; // 实例路径,如 "/users/0/email"
message: string; // 中文错误消息
keyword: string; // 触发关键字,如 "type"、"required"
}
UI 层用关键字徽章(按类型分类着色)+ 路径 + 消息展示,点击错误项可高亮该路径。
十、与 ajv 对比与选型
ajv 是功能最完整的 JSON Schema 校验库,生态成熟。本工具与之对比:
| 维度 | 本工具 | ajv |
|---|---|---|
| 支持规范 | draft-07 核心子集 | draft-04 ~ 2020-12 全部 |
| 关键字覆盖 | 约 80%(核心关键字) | 100%(含 if-then-else、dependencies、contains、propertyNames 等) |
$ref | 仅同文档内部 | 内部 + 外部 + $id 基准 URI |
| format | 9 种内置正则 | 可插拔,支持自定义 format |
| 异步校验 | 不支持 | 支持(用于异步 format) |
| 编译优化 | 无(每次递归) | 预编译为函数,性能高 10x+ |
| bundle 体积 | 约 14KB(gzip 4.5KB) | 约 130KB+(含 draft-07 + format) |
| 适用场景 | 浏览器端快速校验、教学演示 | 服务端严格校验、生产 API、规范一致性 |
选型建议:
- 浏览器端轻量校验、demo、教学 → 本工具或自实现
- 生产 API、需要严格规范一致性、复杂外部引用 → ajv
- 需要编译优化、高频校验 → ajv(预编译)
十一、实战场景
1. API 请求体校验
Express 中间件示例:
const schema = {
type: "object",
required: ["username", "password"],
properties: {
username: { type: "string", minLength: 3, maxLength: 32 },
password: { type: "string", minLength: 8 },
remember: { type: "boolean" }
},
additionalProperties: false
};
app.post("/login", (req, res) => {
const { valid, errors } = validate(req.body, schema);
if (!valid) {
return res.status(400).json({ errors });
}
// ...业务逻辑
});
2. OpenAPI / Swagger
OpenAPI 3.0 的 Schema 对象就是 JSON Schema draft-04 的子集(OpenAPI 3.1 完整对齐 draft-2020-12)。你可以直接用 OpenAPI 文档中的 Schema 定义校验 API 响应:
# openapi.yaml 片段
components:
schemas:
User:
type: object
required: [id, name]
properties:
id:
type: integer
minimum: 1
name:
type: string
minLength: 1
把 components.schemas.User 取出作为 schema,对实际 API 响应做校验,能快速发现接口契约漂移。
3. 配置文件验证
K8s ConfigMap、CI/CD pipeline、应用配置文件都可用 JSON Schema 描述约束,在加载时校验,避免运行时才暴露错误:
{
"type": "object",
"required": ["database", "port"],
"properties": {
"database": {
"type": "object",
"required": ["host", "port"],
"properties": {
"host": { "type": "string", "format": "hostname" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
}
},
"port": { "type": "integer", "default": 8080 }
}
}
十二、与工具矩阵联动
本工具与工具盒子中的其他工具形成 JSON 处理工具集:
- JSON 格式化工具:先用它格式化 Schema 与数据,再粘贴到 Schema 校验工具
- JSONPath 查询工具:校验通过后,用 JSONPath 提取特定字段做进一步分析
- CSV / JSON 互转:CSV 转 JSON 后,用 Schema 校验结构是否符合预期
- YAML / JSON 互转:YAML 配置转 JSON 后校验(K8s 资源校验常用)
典型工作流:
- 用 JSON 格式化 美化从 API 返回的 JSON
- 用 JSON Schema 校验 验证结构是否符合契约
- 用 JSONPath 查询 提取关键字段做断言
总结
JSON Schema 是描述 JSON 数据结构的标准,用关键字声明约束,校验器自动验证。draft-07 核心关键字覆盖:
- 类型:type / enum / const
- 对象:required / properties / patternProperties / additionalProperties / minProperties / maxProperties
- 数组:items / additionalItems / minItems / maxItems / uniqueItems
- 字符串:minLength / maxLength / pattern / format
- 数值:minimum / maximum / exclusiveMinimum / exclusiveMaximum / multipleOf
- 组合:allOf / anyOf / oneOf / not
- 引用:$ref(内部引用)
轻量校验器用递归下降实现,约 380 行覆盖 80% 场景;生产严格校验建议用 ajv。实战场景覆盖 API 校验、OpenAPI 契约验证、配置文件校验,是数据驱动的现代后端工程的基础设施。
立即体验:JSON Schema 校验工具 — 粘贴 Schema 与数据,实时查看校验结果与错误定位。