5月28日 00:31

什么是 JSON Schema?它的作用是什么?

什么是 JSON Schema?

JSON Schema 是一份用 JSON 格式写成的"数据合同",它声明了某类 JSON 数据必须满足的结构、类型和约束规则。你可以把它理解成 JSON 数据的"类型定义 + 校验规则"——不仅规定有哪些字段、字段是什么类型,还能限定取值范围、格式、必填项等。

面试中回答这个问题,记住三个关键词:校验、契约、文档。JSON Schema 同时满足这三个需求,这是它和其他方案的核心差异。

核心作用

数据校验是最根本的用途。拿到一份 JSON,丢给校验器(如 Ajv、python-jsonschema),立刻知道它合不合规,不用手写一堆 if-else。校验器会返回具体的错误路径和原因,排查问题比手动校验快得多。

接口契约——在前后端协作或微服务通信中,JSON Schema 就是双方的数据约定。OpenAPI 3.0 的 schema 字段本质上就是 JSON Schema,用它描述请求体和响应体,API 文档和校验一步到位。不少团队把 Schema 放进代码仓库单独维护,PR 变更时自动触发兼容性检查。

自动生成代码和表单——不少工具能从 Schema 直接生成 TypeScript 类型定义、Go struct、Java POJO,甚至前端表单组件。减少了"文档写了没人看、代码和文档对不上"的问题。反过来看,也有工具(如 typeof-schema)能从现有 TypeScript 类型反向生成 JSON Schema。

一个完整的例子

json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "User", "type": "object", "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "age": { "type": "integer", "minimum": 0, "maximum": 150 }, "email": { "type": "string", "format": "email" }, "role": { "type": "string", "enum": ["admin", "editor", "viewer"] } }, "required": ["name", "email"] }

这段 Schema 做了几件事:限定整体是 object;name 是 1-100 字符的字符串;age 是 0-150 的整数;email 必须符合邮箱格式(format 关键字);role 只能取三个枚举值之一;name 和 email 是必填字段。

常用约束关键词

类别关键词说明
数值minimum, maximum, exclusiveMinimum, exclusiveMaximum限定数值范围
字符串minLength, maxLength, pattern, format长度和正则、格式校验(format 支持 email、uri、date-time 等)
数组minItems, maxItems, uniqueItems, prefixItems元素数量、去重、元组验证
对象required, additionalProperties, minProperties必填字段和额外属性控制
逻辑allOf, anyOf, oneOf, not组合条件,相当于 &&、
条件if/then/else条件验证,当 if 匹配时必须满足 then
引用$ref, $defs复用其他 Schema 定义,避免重复

和 TypeScript 类型、Zod 的区别

这是面试高频追问点。三者都能做数据约束,但定位不同:

TypeScript 类型是编译时工具,只在开发阶段生效,运行时完全消失。它不能校验从网络请求拿到的 JSON 数据——一个 API 返回了错误字段,TypeScript 不会报错,代码照跑,只是逻辑可能出错。

Zod既能在编译时推导类型,也能在运行时校验数据。但它绑定 JavaScript/TypeScript 生态,Schema 本身是代码而非数据,其他语言无法直接消费。

JSON Schema是语言无关的数据格式,任何语言都有校验器实现。它最大的优势是"数据即文档"——Schema 可以直接放进 OpenAPI 规范、配置文件,被各种工具链消费,也能存到数据库里做动态校验。

实际项目中,TypeScript 类型管开发体验,Zod 管运行时校验,JSON Schema 管跨团队、跨语言的数据契约。三者常常搭配使用,并不互斥。例如:用 JSON Schema 定义 API 契约,用工具生成 TypeScript 类型给前端用,后端用 Ajv 在运行时校验请求体。

实际项目中的应用场景

API 网关层校验——在网关(如 Kong、AWS API Gateway)配置 JSON Schema,非法请求在进入业务逻辑前就被拦截,返回 400 而不是让错误数据一路穿透到数据库。这比在每个 handler 里写校验逻辑高效得多。

配置文件校验——VS Code 的 settings.json、ESLint 的 .eslintrc、GitHub Actions 的 workflow 文件都有对应的 JSON Schema,IDE 能据此提供自动补全和实时错误提示。你在 VS Code 里写 settings.json 时弹出的属性提示,背后就是 JSON Schema 在工作。

表单驱动开发——前端框架(如 react-jsonschema-form)根据 Schema 自动渲染表单,包括输入框类型、校验规则、必填标记。后端只用关心 Schema 定义,前后端各写各的,表单逻辑不用重复实现。

消息队列数据校验——在 Kafka、RabbitMQ 等消息系统中,用 JSON Schema Registry 管理消息格式,消费者拿到消息先校验再处理,防止上游数据格式变更导致下游崩溃。

版本差异需要注意

JSON Schema 经历了 Draft-04、Draft-06、Draft-07、Draft 2019-09、Draft 2020-12 等版本。主要变化:

  • Draft-06 起 exclusiveMinimum 从布尔值变成独立数值关键字,minimum 不再排他
  • Draft 2019-09 引入了 $defs 替代 definitions,新增 prefixItems 替代 items 对数组的元组验证,$recursiveRef 实现递归 Schema
  • Draft 2020-12 是当前最新稳定版,用 $dynamicRef 替代了 $recursiveRef

使用时注意校验器支持的版本。Ajv 默认支持 Draft-07,需要显式配置 ajv@draft202012 才能用新特性。Python 的 jsonschema 库默认支持最新版。

面试追问方向

Q: JSON Schema 能做条件验证吗? 可以,用 if/then/else。比如"当 role 是 admin 时,permissions 数组必填":

json
{ "if": { "properties": { "role": { "const": "admin" } } }, "then": { "required": ["permissions"] } }

Q: 如何校验嵌套很深的数据?$ref 引用子 Schema,避免重复定义。配合 $defs(或旧版的 definitions)集中管理公共片段。深层嵌套不会影响校验性能,Ajv 会将整个 Schema 编译成一个校验函数。

Q: JSON Schema 的性能如何? 复杂 Schema 的校验开销不可忽视,但远快于手写校验代码。Ajv 支持 Schema 编译为函数,一次编译多次使用,单次校验通常在微秒级。对于高频场景(如每秒校验上万次请求),建议预编译并缓存。相比之下,python-jsonschema 比 Ajv 慢一个数量级,高并发场景优先选 Ajv。

Q: JSON Schema 和 Protocol Buffers 的 Schema 有什么区别? Protobuf 侧重序列化和跨语言 RPC,自带代码生成和二进制编码,性能更高但不支持动态校验。JSON Schema 侧重验证和文档,数据仍然是 JSON 文本,灵活性更强但序列化体积和速度不如 Protobuf。两者适用场景不同:内部服务间通信选 Protobuf,面向外部 API 或需要人类可读的场景选 JSON Schema。

标签:JSON