You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
}
问题原因
  1. 关键字作用理解偏差:patternProperties的核心作用是给键名匹配对应正则的属性,应用绑定的值校验规则,它本身不承担「强制所有键名必须匹配该正则」的职责。按照JSON Schema规范,未被properties、patternProperties匹配到的键,本应被同级的additionalProperties: false拦截。
  2. 校验器实现兼容问题:部分常用校验库(如旧版本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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:06:26