【问题标题】:How to validate JSON Schema based on combination of an array of enums?如何基于枚举数组的组合验证 JSON Schema?
【发布时间】:2020-11-04 03:12:39
【问题描述】:

给定:

假设我正在为联系人定义一个架构。但是,我可以有“主要联系人”、“学生”或两者兼而有之;以及所有三种选择的不同属性。联系人类型在 contact_type: [ "Primary Contact", "Student" ] 数组中定义,可以是其中之一,也可以是两者。

假设每个联系人类型的字段都是这样的:

  • 如果是主要联系人,那么我需要 phone_number
  • 如果是学生,那么我想要名字
  • 如果是学生和主要联系人,那么我需要 phone_number 和 first_name

用法

我使用Ajv 库在 Node.js 中使用如下代码进行验证:

function validator(json_schema){
    const Ajv = require('ajv');
    const ajv = new Ajv({allErrors: true});
    return ajv.compile(json_schema)
}

const validate = validator(json_schema);

const valid = validate(input);

console.log(!!valid); //true or false
console.log(validate.errors)// object or null

注意:我在使用 anyOf 时遇到了allErrors: true 的问题,我使用allErrors 的输出将所有丢失/无效的字段返回给用户,而不是一次返回一个问题。参考:https://github.com/ajv-validator/ajv/issues/980

架构

我已经编写了以下架构,如果我执行“学生”或“主要联系人”,它就可以工作,但是当我通过两者时,它仍然想验证 ["Student"] 或 ["Primary Contact"] 而不是两者都有。

 {
  "$schema": "http://json-schema.org/draft-07/schema",
  "type": "object",
  "required": [],
  "properties": {},
  "allOf": [
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "allOf": [
                {
                  "type": "string",
                  "const": "Primary Contact"
                },
                {
                  "type": "string",
                  "const": "Student"
                }
              ]
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "phone": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          }
        },
        "required": [
          "phone",
          "first_name"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "type": "string",
              "const": "Student"
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "first_name": {
            "type": "string"
          }
        },
        "required": [
          "first_name"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "type": "string",
              "const": "Primary Contact"
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "phone": {
            "type": "string"
          }
        },
        "required": [
          "phone"
        ]
      }
    }
  ]
}

有效输入示例:

  • 仅适用于 [“主要联系人”]:
    {
        "contact_type":["Primary Contact"],
        "phone":"something"
    }
  • 仅适用于 ["Student"]:
    {
        "contact_type":["Student"],
        "first_name":"something"
    }
  • 对于[“主要联系人”,“学生”]
    {
        "contact_type":["Primary Contact", "Student"],
        "phone":"something",
        "first_name":"something"
    }

问题:

即使allErrors: true,我也想验证一下,这可能吗?如果没有,我应该如何更改架构?

脚注

除非万不得已,否则我不想将“contact_type”更改为数组。 (这是必须的,但只有在别无他法的情况下才能打破)

我不能允许任何附加项,因此我在 if 语句中完全定义了每个对象,尽管 contact_type 很常见。如果我将contact_type 移出,则会收到有关将contact_type 作为附加项传递的错误消息(它会查看if 语句的属性,并且在将其取出到公共位置时看不到contact_type)。这就是为什么我最初的 properties 对象是空的。

【问题讨论】:

  • 这是完全可能的,我非常乐意提供帮助。乍一看,您至少犯了一个简单的错误,因为contains 没有使用模式对象。幸运的是,您可以通过稍微不同的方式实现您想要的。我正在为您制定解决方案
  • 编辑:我错了包含...我需要 mroe coffee =D

标签: javascript node.js jsonschema json-schema-validator ajv


【解决方案1】:

以下是解决验证问题的方法:https://jsonschema.dev/s/XLSDB

这是架构... (如果你尝试打破顾虑会更容易)

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "type": "object",

首先,我们要定义条件检查子模式...

  "definitions": {
    "is_student": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Student"
          }
        }
      }
    },
    "is_primay_contact": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Primary Contact"
          }
        }
      }
    }
  },

接下来,我假设你总是想要contact_type

  "required": ["contact_type"],
  "properties": {
    "contact_type": {
      "type": "array",
      "items": {
        "enum": ["Primary Contact", "Student"]
      }
    },

并且我们需要定义所有允许的属性以防止额外的属性。 (draft-07 不能“看穿”像 allOf 这样的应用程序关键字。您可以使用草稿 2019-09 及更高版本,但那是另一回事了)

    "phone": true,
    "first_name": true
  },
  "additionalProperties": false,

现在,我们需要定义结构约束...

  "allOf": [
    {

如果联系人是学生,则需要名字。

      "if": { "$ref": "#/definitions/is_student" },
      "then": { "required": ["first_name"] }
    },
    {

如果联系人是主要联系人,则需要电话号码。

      "if": { "$ref": "#/definitions/is_primay_contact" },
      "then": { "required": ["phone"] }
    },
    {

但是,另外,如果联系人既是学生又是主要联系人...

      "if": {
        "allOf": [
          { "$ref": "#/definitions/is_student" },
          { "$ref": "#/definitions/is_primay_contact" }
        ]
      },

那么我们需要电话和名字...

      "then": {
        "required": ["phone", "first_name"]
      },

否则,电话或名字中的一个就可以了(上一节已经介绍过)

      "else": {
        "oneOf": [
          {
            "required": ["phone"]
          },
          {
            "required": ["first_name"]
          }
        ]
      }
    }
  ]
 }

我不相信这是最干净的方法,但它确实可以满足您提供的要求。

至于获得验证错误,您可以将其传回给您的 END 用户...鉴于您提出的条件要求,纯 JSON Schema 不是您所期望的...

话虽如此,ajv 确实提供了一个扩展来添加自定义错误消息,考虑到我将验证分解为关注点的方式,它可能可用于添加自定义错误(https://github.com/ajv-validator/ajv-errors )。

【讨论】:

  • 很好的答案。关于“我不相信这是最干净的方法”,使用"additionalProperties": false 时没有干净的方法。有几种选择,但它们都一样糟糕。
  • 小笨蛋:最后一个then 是多余的。它可以被移除。此外,在某些情况下,如果您将 "required": ["contact_type"] 添加到每个定义中,您会收到更好的错误消息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-06
  • 1970-01-01
  • 2018-04-18
  • 2020-11-01
  • 1970-01-01
  • 2015-10-24
  • 2021-12-13
相关资源
最近更新 更多