JSON Schema additionalProperties:false未拦截不符合pattern的属性
问题背景
现有如下接口校验用的Schema定义:
export const MESSAGE_SCHEMA = { additionalProperties: false, type: 'object', properties: { comment: { type: 'string' }, startAt: { type: 'string' }, states: { type: 'object', minProperties: 1, patternProperties: { "^[A-Za-z]+[A-Za-z0-9 ]{0,127}$": { type: 'object', properties: { type: { type: 'string', enum: TASK_TYPES_ALL_ENUM }, next: { type: 'string' }, end: { type: 'boolean' }, choices: { type: 'array', items: { type: 'object' }, minItems: 2 }, default: { type: 'string' }, error: { type: 'string' }, cause: { type: 'string' }, resource: { type: 'string' }, }, required: ['type'], allOf: [ // 当task type = 'choice'时,choices为必填项 { if: { properties: { type: { const: TASK_TYPE_CHOICE } } }, then: { required: ['type', 'choices'] } } ] } }, additionalProperties: false } }, required: ['startAt','states'] };
实际校验时出现不符合预期的表现:states对象内的键名不匹配patternProperties指定的正则规则时,请求会被直接放行,不会触发校验错误。
- 异常复现案例:
states下键名为!#@^@^,不符合正则规则,按配置本应拦截,但接口返回校验成功,请求体如下:
{ "eventNamespace": "state", "eventType": "transition", "payload": { "foo": "bar" }, "message": { "startAt": "foo", "states": { "!#@^@^": { "type": "choice", "choices": [] } } } }
- 对照测试案例:将
states下的键名替换为符合正则规则的valid property example时,会正常触发内部字段校验,返回400错误:
{ "statusCode": 400, "error": "Bad Request", "message": "body/message/states/valid property example/choices must NOT have fewer than 2 items" }
问题原因
- 关键字作用理解偏差:
patternProperties的核心作用是给键名匹配对应正则的属性,应用绑定的值校验规则,它本身不承担「强制所有键名必须匹配该正则」的职责。按照JSON Schema规范,未被properties、patternProperties匹配到的键,本应被同级的additionalProperties: false拦截。 - 校验器实现兼容问题:部分常用校验库(如旧版本Ajv,也是大部分Node.js后端框架默认的校验引擎),在对象节点未显式声明
properties、且子规则使用了if/then/allOf这类条件组合关键字时,会出现额外属性判断逻辑失效的问题。
这也解释了两个测试案例的表现差异:当键名匹配patternProperties的正则时,校验器会正常进入值校验流程,触发choices长度错误;当键名不匹配正则时,既不会触发值校验,也没有被额外属性逻辑拦截,直接被放行。
修复方案
使用JSON Schema中专门用于约束属性名规则的propertyNames关键字,显式强制states下所有键名必须符合正则要求,从根源上避免校验器逻辑兼容导致的漏过问题。
修改后的states节点Schema如下:
states: { type: 'object', minProperties: 1, // 新增:显式校验所有属性名必须匹配指定正则 propertyNames: { pattern: "^[A-Za-z]+[A-Za-z0-9 ]{0,127}$" }, patternProperties: { "^[A-Za-z]+[A-Za-z0-9 ]{0,127}$": { // 保留原有state属性值的全部校验规则即可 type: 'object', properties: { type: { type: 'string', enum: TASK_TYPES_ALL_ENUM }, next: { type: 'string' }, end: { type: 'boolean' }, choices: { type: 'array', items: { type: 'object' }, minItems: 2 }, default: { type: 'string' }, error: { type: 'string' }, cause: { type: 'string' }, resource: { type: 'string' }, }, required: ['type'], allOf: [ { if: { properties: { type: { const: TASK_TYPE_CHOICE } } }, then: { required: ['type', 'choices'] } } ] } }, additionalProperties: false }
调整后效果:
- 所有
states下的键名会被优先校验,!#@^@^这类不符合正则的键名会直接触发校验错误,不会进入后续值校验流程 - 原有
patternProperties和additionalProperties: false规则可保留作为双层兜底,既保证合法键名对应的值被正确校验,也能拦截其他未声明的异常属性。
内容的提问来源于stack exchange,提问作者StratusBase LLC
相关产品推荐
相关产品推荐

