SchemaSafe 博客

常见的 JSON 模式编写错误

JSON 模式(目前为 Draft 2020-12)是描述和验证 JSON 结构的标准方式。大多数有问题的模式并不是语法错误,而是宽容的错误,允许通过无效的数据。在代码审查中,最常出现的四种错误是缺少必需的数组、开放的 additionalProperties、滥用的类型联合以及指向无处的 $ref。

忘记了必需的数组

当你在 properties 下添加一个属性时,你只描述了它的形状,而不是要求它的存在。如果必需的数组省略了该键,则没有该字段的实例仍然可以通过验证。这是一个静默通过,因为验证器将该属性视为可选的。我见过团队发布的 API 中,一个关键标识符从必需数组中缺失,导致下游的空指针错误仅在生产环境中出现。

例如,一个用户模式,期望一个 id 应该包含:

{ "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } }, "required": ["id"] } 如果没有 required 行,一个只有 {"name":"Ada"} 的对象仍然可以通过,即使 API 后来将 id 视为必需的。

保持 additionalProperties 开放

additionalProperties 的默认值为 true。这允许任何未在 properties 中定义的键通过验证。例如,输入 "user_name" 而不是 "username",就会成为一个被默默接受的字段,污染数据模型并使后续迁移更加困难。我曾见过团队花费数周时间清理那些进入数据库的杂乱键,因为模式从未拒绝过它们。

这是产品模式的严格版本:

{ "type": "object", "properties": { "sku": { "type": "string" }, "price": { "type": "number", "minimum": 0 } }, "required": ["sku","price"], "additionalProperties": false } 使用此模式,包含 {"sku":"ABC","price":10,"extra":1} 的实例将无法通过验证,从而提早捕获错误。

滥用类型联合

类型关键字期待一个字符串或字符串数组。写入"type": "string, number"是无效的JSON Schema;解析器将整个值视为单个未知类型,许多验证器会默默地忽略它,从而允许任何值通过。另一个常见的错误是省略可选字段的类型,这使得验证器跳过该键的类型检查。

可空日期字段的示例:

{ "type": "object", "properties": { "issuedAt": { "type": ["string","null"], "format": "date-time" } }, "required": ["issuedAt"] } 在这里,null是允许的,所以{"issuedAt":null}通过,而{"issuedAt":"not-a-date"}失败。

$ref 陷阱和版本漂移

一个解析为缺失位置的 $ref 会导致整个模式在加载时失败,这是一个噪音问题,容易被发现。一个更安静的问题是引用解析但指向错误的定义,因为草稿版本发生了变化或文件夹结构发生了变化。在 2020-12 草稿中,重用块的关键字从 definitions 移动到了 $defs 。如果您保留旧键,模式仍然有效(旧关键字被忽略),但您会失去重用定义的能力,从而导致代码重复和漂移。

正确引用的示例:

{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } }, "required": ["street","city"] } }, "type": "object", "properties": { "billing": { "$ref": "#/$defs/address" }, "shipping": { "$ref": "#/$defs/address" } }, "required": ["billing","shipping"] } 如果您错误地写了 "$ref": "#/definitions/address",则引用将解析为 undefined,验证器将抛出错误,提醒您存在不匹配。

在发布前使用样本进行验证

最廉价的信心检查是运行一批代表性的 JSON 实例通过验证器,然后再将模式视为合同。一个好的测试套件至少包含一个有效的示例,匹配预期的使用方式,以及一个故意破坏每个规则的无效示例。如果模式接受好的样本并拒绝坏的样本,你就有证据证明它不是过于宽松的。我见过团队跳过这一步,后来发现缺少必需的字段允许没有产品 ID 的订单通过,从而导致履约错误。

const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true}); const schema = require('./user-schema.json'); const good = { "id": "123", "name": "Ada" }; const bad = { "name": "Ada" }; // 缺少 id const validate = ajv.compile(schema); console.log('好的样本:', validate(good)? '通过' : '失败'); if (!validate(good)) console.error(validate.errors); console.log('坏的样本:', validate(bad)? '通过' : '失败'); if (validate(bad)) console.error('意外地接受了坏的样本'); 在 CI 中运行此代码可以确保宽松的错误永远不会到达生产环境。

这不是什么

本指南并不承诺,遵循这些点可以使您的模式免受每种可能的错误。JSON Schema是一种灵活的语言,随着您使用自定义关键字扩展它或将其与其他工具(如OpenAPI)结合使用,新的陷阱可能会出现。我并不是声称列出的四个错误是唯一重要的错误;它们只是我所见过的代码审查中出现最频繁的错误。这里的建议基于我维护SchemaSafe的经验和公开的规范;它不是替代阅读官方规范或运行您自己的验证测试的替代品。

参考资料

https://ajv.js.org/json-schema.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

https://json-schema.org

https://json-schema.org/draft/2020-12/json-schema-core.html

常见问题

在 properties 中声明一个属性并不使其成为必需的。您必须将其列在 required 数组中。该数组是 properties 的同级,而不是嵌套在其中。

它默认为 true,因此未知键会通过。设置 additionalProperties: false,其中严格性很重要,或者使用模式约束扩展字段。开放模式会让打字错误悄悄通过。

从 required 中省略它,并在其类型数组中包含 "null",例如 ["string", "null"]。编写逗号分隔的类型字符串是无效的,并且可能会使约束失效。

在 $defs 下。较旧的 definitions 键仍然有效,但标记为旧草案。始终固定 $schema,以便验证器应用您期望的草案。

尝试产品:SchemaSafe

2026-09-16 · 仅主源 · 无伪造流量指标

相关文章

不使用LLM的JSON Schema验证(2026)

探索完整的AI微SaaS主题矩阵 →

为什么我的模式接受缺失字段?

在 properties 中声明一个属性并不使其成为必需的。你必须在 required 数组中列出它。该数组是 properties 的同级,而不是嵌套在其中。

additionalProperties 默认做什么?

它默认为 true,因此未知键会通过。对于需要严格性的地方,请设置 additionalProperties: false,或者使用一个模式来约束扩展字段。开放模式会让打字错误默默通过。

如何在 JSON 模式中标记一个字段为可选?

省略 required 中的字段并包含

可重用定义在 Draft 2020-12 中放在哪里?

在 $defs 下。较旧的 definitions 键仍然有效,但标记为旧草案。始终固定 $schema,以便验证器应用您期望的草案。

仅供决策参考,不代替正式的模式审查或认证审计。

← 返回博客

这次结果有帮助吗?反馈