如何根据枚举数组的组合验证 JSON 模式?
How to validate JSON Schema based on combination of an array of enums?
给定:
假设我正在为联系人定义架构。但是,我可以有“主要联系人”、“学生”或两者兼有;以及所有三种选择的不同属性。联系人类型在 contact_type: [ "Primary Contact", "Student" ]
数组中定义,可以是其中之一,也可以是两者。
假设每个联系人类型的字段都是这样的:
- 如果是主要联系人,那么我想要phone_number
- 如果是学生,那我要first_name
- 如果学生和主要联系人那么我想要 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
的输出到 return 所有 missing/invalid 字段返回给用户,而不是 return 一次解决一个问题。参考:https://github.com/ajv-validator/ajv/issues/980
架构
我已经编写了以下架构,如果我执行“Student”或“Primary Contact”,它就可以工作,但是当我通过两者时,它仍然想要针对 [“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"
}
- 仅["学生"]:
{
"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
作为 additionalItem 传递的错误消息(它会查看 if 语句的属性,但在将其取出到公共区域时不会看到 contact_type地方)。这就是为什么我的初始 properties
对象是空的。
以下是我可能会如何解决验证问题: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"] }
},
{
如果联系人是主要联系人,则需要 phone。
"if": { "$ref": "#/definitions/is_primay_contact" },
"then": { "required": ["phone"] }
},
{
但是,另外,如果联系人既是学生又是主要联系人...
"if": {
"allOf": [
{ "$ref": "#/definitions/is_student" },
{ "$ref": "#/definitions/is_primay_contact" }
]
},
然后我们需要 phone 和名字...
"then": {
"required": ["phone", "first_name"]
},
否则,phone 或名字中的一个都可以(上一节介绍了哪个)
"else": {
"oneOf": [
{
"required": ["phone"]
},
{
"required": ["first_name"]
}
]
}
}
]
}
我不认为这是最干净的方法,但它确实可以满足您提供的要求。
至于获取验证错误,您可以将其传回给您的最终用户...鉴于您提出的条件要求,这不是您可以使用纯 JSON 模式期望的...
话虽如此,ajv 确实提供了添加自定义错误消息的扩展,考虑到我将验证分解为关注点的方式,它可能可用于添加您想要的自定义错误(https://github.com/ajv-validator/ajv-errors).
给定:
假设我正在为联系人定义架构。但是,我可以有“主要联系人”、“学生”或两者兼有;以及所有三种选择的不同属性。联系人类型在 contact_type: [ "Primary Contact", "Student" ]
数组中定义,可以是其中之一,也可以是两者。
假设每个联系人类型的字段都是这样的:
- 如果是主要联系人,那么我想要phone_number
- 如果是学生,那我要first_name
- 如果学生和主要联系人那么我想要 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
的输出到 return 所有 missing/invalid 字段返回给用户,而不是 return 一次解决一个问题。参考:https://github.com/ajv-validator/ajv/issues/980
架构
我已经编写了以下架构,如果我执行“Student”或“Primary Contact”,它就可以工作,但是当我通过两者时,它仍然想要针对 [“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"
}
- 仅["学生"]:
{
"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
作为 additionalItem 传递的错误消息(它会查看 if 语句的属性,但在将其取出到公共区域时不会看到 contact_type地方)。这就是为什么我的初始 properties
对象是空的。
以下是我可能会如何解决验证问题: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"] }
},
{
如果联系人是主要联系人,则需要 phone。
"if": { "$ref": "#/definitions/is_primay_contact" },
"then": { "required": ["phone"] }
},
{
但是,另外,如果联系人既是学生又是主要联系人...
"if": {
"allOf": [
{ "$ref": "#/definitions/is_student" },
{ "$ref": "#/definitions/is_primay_contact" }
]
},
然后我们需要 phone 和名字...
"then": {
"required": ["phone", "first_name"]
},
否则,phone 或名字中的一个都可以(上一节介绍了哪个)
"else": {
"oneOf": [
{
"required": ["phone"]
},
{
"required": ["first_name"]
}
]
}
}
]
}
我不认为这是最干净的方法,但它确实可以满足您提供的要求。
至于获取验证错误,您可以将其传回给您的最终用户...鉴于您提出的条件要求,这不是您可以使用纯 JSON 模式期望的...
话虽如此,ajv 确实提供了添加自定义错误消息的扩展,考虑到我将验证分解为关注点的方式,它可能可用于添加您想要的自定义错误(https://github.com/ajv-validator/ajv-errors).