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

如何为指定数据编写正确的Joi Schema验证?

问题:Joi验证Schema报错"refinementGoals[2].value" is not allowed

待验证的输入数据

{
  "submissionReason": "new TPV required",
  "alignerInfo": {
    "name": "UPPER",
    "value": [
      {
        "name": "set",
        "value": "18/20"
      },
      {
        "name": "zenStage",
        "value": "2"
      }
    ]
  },
  "archesToTreat": "BOTH",
  "refinementGoals": [
    {
      "name": "Alignment"
    },
    {
      "name": "Close Residual Space",
      "value": [
        {
          "position": [
            "2.1",
            "2.2"
          ],
          "value": "0.5"
        },
        {
          "position": [
            "2.4",
            "2.5"
          ],
          "value": "0.7"
        }
      ]
    },
    {
      "name": "Resolve posterior Open Bite",
      "value": [
        {
          "name": "Attachments Only",
          "value": {
            "name": "teeth Attachment",
            "value": [
              "2.1",
              "2.2"
            ]
          }
        },
        {
          "name": "Add Button for elastic",
          "value": [
            "2.1",
            "2.2"
          ]
        }
      ]
    }
  ],
  "attachments": "keep all previous attachment and change",
  "iprInfo": {
    "name": "ipr not performed as prescibed",
    "value": [
      {
        "position": [
          "2.1",
          "2.2"
        ],
        "value": "0.5"
      }
    ]
  },
  "specialNotes": "Doctor Notes"
}

当前使用的验证代码

Joi.object({
  submissionReason: Joi.string().required(),
  alignerInfo: Joi.object({
    name: Joi.string().required(),
    value: Joi.array().items(Joi.object({
      name: Joi.string().required(),
      value: Joi.string().required(),
    })).required()
  }).required(),
  archesToTreat: Joi.string().valid('UPPER', 'LOWER', 'BOTH').required(),
  refinementGoals: Joi.array().items(Joi.object({
    name: Joi.string().required(),
    value: Joi.array().items(Joi.object({
      position: Joi.array().items(Joi.string().required()).required(),
      value: Joi.string().required(),
      name: Joi.string().forbidden()
    })).when('name', {
      is: 'Close Residual Space',
      then: Joi.required(),
      otherwise: Joi.forbidden()
    })
  })).required(),
  attachments: Joi.string().required(),
  iprInfo: Joi.object({
    name: Joi.string().required(),
    value: Joi.array().items(Joi.object({
      position: Joi.array().items(Joi.string().required()).required(),
      value: Joi.string().required()
    })).required()
  }).required(),
  specialNotes: Joi.string().required()
})

问题原因

当前的refinementGoals验证规则有两个核心问题:

  1. 仅允许name为"Close Residual Space"的项存在value,其他所有name的value都被标记为forbidden,但实际数据中"Resolve posterior Open Bite"也有合法的value字段,因此触发报错。
  2. 定义的value结构仅适配"Close Residual Space"的情况,无法匹配"Resolve posterior Open Bite"的value结构(后者的子项包含name和不同格式的value)。

正确的验证方案

需要针对refinementGoals中不同的name值,分别定义对应的value结构规则:

Joi.object({
  submissionReason: Joi.string().required(),
  alignerInfo: Joi.object({
    name: Joi.string().required(),
    value: Joi.array().items(Joi.object({
      name: Joi.string().required(),
      value: Joi.string().required(),
    })).required()
  }).required(),
  archesToTreat: Joi.string().valid('UPPER', 'LOWER', 'BOTH').required(),
  refinementGoals: Joi.array().items(
    Joi.object({
      name: Joi.string().valid('Alignment', 'Close Residual Space', 'Resolve posterior Open Bite').required()
    })
    // 针对Alignment的规则:禁止value字段
    .when('name', {
      is: 'Alignment',
      then: Joi.object({ value: Joi.forbidden() })
    })
    // 针对Close Residual Space的规则:value为固定结构的数组
    .when('name', {
      is: 'Close Residual Space',
      then: Joi.object({
        value: Joi.array().items(Joi.object({
          position: Joi.array().items(Joi.string().required()).required(),
          value: Joi.string().required(),
          name: Joi.forbidden()
        })).required()
      })
    })
    // 针对Resolve posterior Open Bite的规则:value为另一种结构的数组
    .when('name', {
      is: 'Resolve posterior Open Bite',
      then: Joi.object({
        value: Joi.array().items(Joi.object({
          name: Joi.string().required(),
          value: Joi.alternatives().try(
            // 匹配teeth Attachment的对象结构
            Joi.object({
              name: Joi.string().required(),
              value: Joi.array().items(Joi.string().required()).required()
            }),
            // 匹配直接的牙齿数组结构
            Joi.array().items(Joi.string().required())
          ).required()
        })).required()
      })
    })
  ).required(),
  attachments: Joi.string().required(),
  iprInfo: Joi.object({
    name: Joi.string().required(),
    value: Joi.array().items(Joi.object({
      position: Joi.array().items(Joi.string().required()).required(),
      value: Joi.string().required()
    })).required()
  }).required(),
  specialNotes: Joi.string().required()
})

关键修改点

  • 给name字段添加valid约束,限定允许的取值范围,避免非法值。
  • 对三种不同的name值分别定义value规则:
    • "Alignment":禁止value字段
    • "Close Residual Space":保持原有的position+value结构验证
    • "Resolve posterior Open Bite":适配其value的两种子结构(对象或字符串数组)

内容的提问来源于stack exchange,提问作者Dr Doom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 12:07:34