正文
假设会议时长应当是分钟数,收到的值却是“半小时”;或者字段名称本来约定为 title,调用方却写成了 topic。两份数据都可能符合 JSON 语法,但后续程序未必知道怎样处理。
上一篇解决的是数据怎样表达与传递;这一篇要继续解决:对于某个具体接口,什么样的数据才符合约定。
Schema 可以理解为一份描述数据形状和规则的约定。数据校验则是拿具体数据与这些规则比较,找出哪些地方符合要求、哪些地方不符合。
把 Schema 写出来,只是把约定明确保存下来。要发生实际检查,程序还需要调用能够理解这些规则的校验工具,并处理校验结果。把规则放在说明文档里,却没有在接收数据时检查,错误仍然可能进入后续流程。
客户端可以先检查,帮助使用者尽早发现问题;服务端也应当根据自身约定检查收到的输入。不能只因为另一端声称“已经校验过”,就把本来需要落实的输入边界省略掉。
不同系统可以采用不同形式的 Schema。数据库中的表结构、接口描述和程序里的数据模型,都可能承载相关约定。这里以 JSON Schema 为例,先看会议通知的数据怎样被检查。
JSON Schema 使用一组关键字描述 JSON 值。例如,type 说明允许的数据类型;对象规则可以描述成员;数组规则可以约束其中的元素。规则本身也可以用 JSON 表达,但它描述的是另一份数据应满足什么条件。JSON Schema 的类型与规则
假设我们设计一个保存通知草稿的接口,决定只接受会议编号、标题、时长和保存模式,可以先写出下面这份教学用 Schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"meeting_id": {"type": "string", "minLength": 1},
"title": {"type": "string", "minLength": 1},
"duration_minutes": {
"type": "integer",
"minimum": 1,
"maximum": 240
},
"mode": {"enum": ["draft"]}
},
"required": ["meeting_id", "title", "duration_minutes", "mode"],
"additionalProperties": false
}
Schema 和被检查的数据都可能带着花括号,阅读时要分清它们的角色。实际数据中的 title 保存会议标题;Schema 中对 title 的描述,则说明这个位置允许放什么样的值。把一份 Schema 发给程序,不会因此创建会议或保存草稿。
这份例子没有连接真实服务。第一项 $schema 声明采用哪一种 JSON Schema 方言,帮助校验工具按相应版本理解关键字。不同版本或不同产品支持的范围可能不同,不能只把规则文本复制过去就假定一定兼容。
最外层的 type 为 object,要求被检查的数据是对象。properties 列出了我们认识的成员:meeting_id 和 title 应当是字符串,minLength 为 1,表示字符串至少有一个字符。
duration_minutes 要求整数,并限制在 1 到 240 之间。这里的上限是教学例子中人为制定的接口规则,不是 JSON Schema 对所有会议时长的通用规定。
这里的 integer 按数值是否为整数判断。比如 30 和 30.0 表达的值都可以满足它,字符串“30”则不同。这与 Python 在运行时区分 int 和 float 的方式,属于不同层次的约定。JSON Schema 怎样判断整数
mode 的 enum 列出允许值,这里只有 draft,表示这个接口只接受草稿模式。required 要求四个成员必须出现;additionalProperties 为 false,则拒绝规则中没有声明的其他成员。
需要注意,单独把一个名字写进 properties,并不会自动让它成为必填项。required 才承担这里的必填约束。允许哪些额外成员,也需要按实际需求决定;当前例子使用的是一种明确限制输入形状的设计。对象成员、必填与额外字段
现在再拿一份具体数据来比较:
{
"meeting_id": "m42",
"title": "项目例会通知",
"duration_minutes": 30,
"mode": "draft"
}
按刚才列出的规则,这份数据应当通过校验。这是根据 Schema 推导出的示例结果,不是实际调用保存接口后得到的运行记录。
如果把 30 改成字符串“30”,它仍是合法 JSON,却不满足整数要求。如果删除 title,就缺少必填成员;如果把 mode 改成 send,就不在允许值中;额外加入 send_now,也会被当前的额外字段规则拒绝。
这些错误最好在真正调用保存工具之前发现。程序可以把问题表达为“duration_minutes 应为整数,当前收到字符串”,让开发者或模型知道需要改哪里,而不只是返回一句“失败”。
不过,自动“修好”数据也需要谨慎。
有些程序库能够把字符串“30”转换为数字 30,这种转换需要程序库或应用另外提供,不能把它当成 JSON Schema 的默认行为。接口应该明确是否接受转换,避免一端认为它会自动处理,另一端却严格拒绝。
更模糊的“半小时”,则可能需要语言理解和业务判断。系统可以重新取得符合约定的数据,但不能因为想让校验通过,就随意删掉不理解的字段,或填入没有依据的会议时间。
必填与空值也需要分别认识。required 只要求成员存在;能不能使用 null,要看该成员的类型规则。如果 title 要求字符串,把值设成 null 仍然不符合要求。
另一方面,minLength 为 1 只能排除空字符串。一个只包含空格的标题,也可能达到这个长度。若业务要求“去掉首尾空格以后仍有有效文字”,就需要额外规则或程序处理。这说明每一条约束都有具体作用范围。
Schema 还可以带说明或默认值,但“写了默认值”不等于工具一定会帮你填进去。JSON Schema 的 default 主要提供注释信息,自动填值需要具体应用实现并说明。说明、默认值与校验的区别
时间字段也有类似边界。我们可以约定开始时间是一段字符串,并使用 date-time 格式说明它应符合相应形式;但在 2020-12 方言中,format 是否作为实际拒绝不合格值的检查,还取决于所用词汇表、校验器能力和配置。不能只看到这个关键字,就断定日期已经严格检查。format 的注释与断言行为
即使格式检查真的启用,仍然不能回答所有问题。
“2026-10-23T15:00:00+08:00”可以是形式合适的日期时间,但它是不是项目例会的真实时间,需要与日历来源核对。时长 30 满足数值范围,也不能证明会议实际上持续半小时。
同样,meeting_id 是非空字符串,不代表这场会议存在;mode 是 draft,也不代表当前账户有权在任何文件夹保存草稿。结构、事实与权限,是不同层面的检查。
这三层可以在程序中接着进行:先解析 JSON,再按 Schema 检查结构与部分值;然后结合业务规则与可信数据核实内容;执行前还要落实访问权限和当前任务授权。并不是每一层都要求用户点一次确认,许多检查可以由程序完成。
部分业务规则也能进一步表达进 Schema,例如某个字段出现时,另一个字段必须存在。遇到涉及外部状态、跨记录关系或实际操作的判断,则需要结合应用处理。Schema 的能力应当按具体规则与工具理解,不能笼统地说它只能检查类型,或可以证明所有正确性。
数据进入系统时值得检查,工具返回结果时也一样。日历接口升级后,如果字段名称变化,尽早发现不符合约定,比让模型继续根据残缺数据编写通知更容易定位问题。
当规则本身调整时,还要考虑兼容。例如,新版要求必须有地点,旧记录却没有;或者新增一个可选说明,旧版程序会不会因为禁止额外字段而拒绝它。规则越明确,越需要让调用双方知道采用的是哪一份约定。
对于模型生成的工具参数,Schema 可以帮助限定输出形状,也可以让程序在执行前发现不合格输入。但参数通过校验,仍然只是一个检查结果。保存文件需要实际调用工具,是否成功还要读取执行反馈。
学到这里,我们已经认识了程序间通信、数据表示和数据规则。下一篇将进入异步编程,看看程序等待日历响应或文件操作时,怎样组织等待与后续处理。
当前学习编号:02.06 Schema 与数据校验。
下一学习编号:02.07 异步编程。后续还有其他学习编号。
关联分支:模型与工具约定对应 12.07 JSON Schema 约束、16.03 工具输入 Schema、16.06 参数校验与默认值、16.07 工具结果结构与错误格式。
校验与执行边界对应 16.05 工具调用请求与实际执行的区别、16.08 工具结果验证、22.07 用户意图、授权与操作审批;规则变更对应 21.18 文件格式与模式演进、02.12 单元测试与集成测试。这些支系会在各自编号中单独展开。