Javascript 如何基于枚举数组的组合验证JSON模式? 鉴于:

Javascript 如何基于枚举数组的组合验证JSON模式? 鉴于:,javascript,node.js,jsonschema,json-schema-validator,ajv,Javascript,Node.js,Jsonschema,Json Schema Validator,Ajv,假设我正在为联系人定义一个模式。但是,我可以有“主要联系人”、“学生”或两者兼而有之的人;三种选择都有不同的属性。联系人类型在联系人类型:[“主要联系人”、“学生”]的数组中定义,可以是一个,也可以是两个 假设每个联系人类型的字段如下所示: 如果是主要联系人,那么我要电话号码 如果是学生,那么我要名字 如果是学生和主要联系人,那么我想要电话号码和名字 用法 我使用库在Node.js中使用如下代码进行验证: function validator(json_schema){ const

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

假设每个联系人类型的字段如下所示:

  • 如果是主要联系人,那么我要电话号码
  • 如果是学生,那么我要名字
  • 如果是学生和主要联系人,那么我想要电话号码和名字
用法 我使用库在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
的输出将所有缺失/无效字段返回给用户,而不是一次返回一个问题。参考:

模式 我已经编写了下面的模式,如果我选择“学生”或“主要联系人”,它会起作用,但当我通过这两个选项时,它仍然希望针对[“学生”]或[“主要联系人”]进行验证,而不是同时对这两个选项进行验证

 {
  "$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"
        ]
      }
    }
  ]
}
有效输入示例:
  • 仅针对[“主要联系人”]:
  • 仅针对[“学生”]:
  • 对于[“主要联系人”、“学生”]
问题: 即使
allErrors:true
,我也希望验证这一点,这可能吗?如果没有,我应该如何更改模式

脚注 除非万不得已,否则我不想将“联系人类型”从数组更改为数组。(这是一项要求,但只有在没有其他办法的情况下才能打破)


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

以下是我解决验证问题的方法:

这是模式。。。 (如果你试图消除顾虑,会更容易)

首先,我们想定义我们的条件检查子模式

  "definitions": {
    "is_student": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Student"
          }
        }
      }
    },
    "is_primay_contact": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Primary Contact"
          }
        }
      }
    }
  },
接下来,我假设您总是希望
联系方式

  "required": ["contact_type"],
  "properties": {
    "contact_type": {
      "type": "array",
      "items": {
        "enum": ["Primary Contact", "Student"]
      }
    },
我们需要定义所有允许的属性,以防止附加属性。(
draft-07
无法“看穿”涂抹器关键字,如
allOf
。您可以使用draft
2019-09
及更高版本,但这是另一个故事)

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

  "allOf": [
    {
如果联系人是学生,则需要姓名

      "if": { "$ref": "#/definitions/is_student" },
      "then": { "required": ["first_name"] }
    },
    {
      "if": { "$ref": "#/definitions/is_primay_contact" },
      "then": { "required": ["phone"] }
    },
    {
如果联系人是主要联系人,则需要电话

      "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"]
      },
否则,电话或名字中的任何一个都可以(上一节介绍了哪一个)

我不相信这是最干净的方法,但它确实适用于您提供的需求

至于获取验证错误,您可以传回给最终用户。。。考虑到您列出的条件需求,这不是纯JSON模式所能期望的


话虽如此,ajv确实提供了一个扩展来添加自定义错误消息,鉴于我将验证分解为关注点的方式,它可能可以用于添加自定义错误,正如您希望做的那样()。

这完全是可能的,我非常乐意提供帮助。乍一看,您至少犯了一个简单的错误,
contains
不接受模式对象。幸运的是,你想要的东西可以通过稍微不同的方式实现。我正在为你制定一个解决方案编辑:我对包含的内容错了。。。我需要你的回答。关于“我不相信这是最干净的方法”,当使用
“additionalProperties”:false
时,没有干净的方法。有几种选择,但它们都一样糟糕。小nit:最后一个
然后
是多余的。它可以被移除。此外,在某些情况下,如果向每个定义中添加
“required”:[“contact_type”]
,您将获得更好的错误消息。
      "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"]
          }
        ]
      }
    }
  ]
 }