有条件地应用子模式
必要依赖
dependentRequired
关键字有条件地要求,如果一个对象存在某个特定的属性,则另一个属性也必须存在。例如,假设我们有一个表示客户的模式,如果您有他们的信用卡号,您还需要确保您有账单地址。如果您没有他们的信用卡号,则不需要帐单邮寄地址。我们使用dependentRequired
关键字表示一个属性对另一个属性的这种依赖性。dependentRequired
关键字的值是一个对象。对象中的每个条目都从属性的名称p映射到一个字符串数组,其中列出了p存在时所需的属性。
在下面的例子中,无论何时,只要存在credit_card
,另一个属性billing_address
属性必须存在:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependentRequired": {
"credit_card": ["billing_address"]
}
}
// OK
{
"name": "John Doe",
"credit_card": 5555555555555555,
"billing_address": "555 Debtor's Lane"
}
// not OK,这个实例有一个 credit_card,但缺少一个 billing_address。
{
"name": "John Doe",
"credit_card": 5555555555555555
}
// OK。这没关系,因为我们既没有 credit_carda 也没有 billing_address。
{
"name": "John Doe"
}
// OK。请注意,依赖项不是双向的。有一个没有信用卡号的帐单地址是可以的。
{
"name": "John Doe",
"billing_address": "555 Debtor's Lane"
}
要解决上面的最后一个问题(依赖项不是双向的),您当然可以明确定义双向依赖项:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependentRequired": {
"credit_card": ["billing_address"],
"billing_address": ["credit_card"]
}
}
// not OK,这个实例有一个 credit_card,但缺少一个 billing_address。
{
"name": "John Doe",
"credit_card": 5555555555555555
}
// not OK,这有一个 billing_address,但缺少一个 credit_card。
{
"name": "John Doe",
"billing_address": "555 Debtor's Lane"
}
Draft 4-7Draft2019-09 之前的版本,
dependentRequired
和dependentSchemas
被称为一个关键字dependencies
。如果依赖值是一个数组,它的行为就像一个dependentRequired
,如果依赖值是一个模式,它的行为就像dependentSchema
.
模式依赖
dependenciesSchemas
关键字要求当给定的属性存在时,有条件地应用子模式。此架构的应用方式与allOf应用架构的方式相同。没有合并或扩展任何内容。两种模式独立应用。
例如,这里有另一种写法:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" }
},
"required": ["name"],
"dependentSchemas": {
"credit_card": {
"properties": {
"billing_address": { "type": "string" }
},
"required": ["billing_address"]
}
}
}
// OK
{
"name": "John Doe",
"credit_card": 5555555555555555,
"billing_address": "555 Debtor's Lane"
}
// not OK,这个实例有一个 credit_card,但缺少一个 billing_address:
{
"name": "John Doe",
"credit_card": 5555555555555555
}
// OK。这有一个 billing_address,但缺少一个 credit_card。这通过了,因为这里 billing_address 看起来像一个附加属性:
{
"name": "John Doe",
"billing_address": "555 Debtor's Lane"
}
Draft 4-7 Draft2019-09 之前的版本,
dependentRequired
和dependentSchemas
被称为一个关键字dependencies
。如果依赖值是一个数组,它的行为就像一个dependentRequired
,如果依赖值是一个模式,它的行为就像dependentSchema
.
条件语句
新的 Draft7 中 if
,then
和else
关键字允许基于另一种模式的结果来应用子模式,这很像传统编程语言中的if
/ then
/else
构造。
如果if
有效,then
也必须有效(并被else
忽略)。如果 if
无效,else
也必须有效(并被then
忽略)。
如果then
或else
未定义,则if
表现为它们的值为true
。
如果then
和/或else
出现在没有if
,then
和 的模式中,else
则被忽略。
我们可以把它放在真值表的形式中,显示 when if
, then
, and else
are valid 的组合 以及整个模式的结果有效性:
if | then | else | whole schema |
---|---|---|---|
T | T | n/a | T |
T | F | n/a | F |
F | n/a | T | T |
F | n/a | F | F |
n/a | n/a | n/a | T |
例如,假设您想编写一个模式来处理美国和加拿大的地址。这些国家/地区有不同的邮政编码格式,我们希望根据国家/地区选择要验证的格式。如果地址在美国,则该postal_code
字段是“邮政编码”:五个数字后跟可选的四位后缀。如果地址在加拿大,则该postal_code
字段是一个六位字母数字字符串,其中字母和数字交替出现。
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"default": "United States of America",
"enum": ["United States of America", "Canada"]
}
},
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
},
"else": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
}
// OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"country": "United States of America",
"postal_code": "20500"
}
// OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"postal_code": "20500"
}
// OK
{
"street_address": "24 Sussex Drive",
"country": "Canada",
"postal_code": "K1M 1M4"
}
// not OK
{
"street_address": "24 Sussex Drive",
"country": "Canada",
"postal_code": "10000"
}
// not OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"postal_code": "K1M 1M4"
}
笔记 :在此示例中,“国家/地区”不是必需的属性。因为“if”模式也不需要“country”属性,它会 pass 然后应用“then”模式。因此,如果未定义“country”属性,则默认行为是将“postal_code”验证为美国邮政编码。“default”关键字没有效果,但将其包含在模式中,对读者比较友好,可以更容易地识别默认行为。
不幸的是,上面的这种方法不能扩展到两个以上的国家。但是,您可以将if
和then
包裹在allOf
中以创建可扩展的内容。在此示例中,我们将使用美国和加拿大邮政编码,但还会添加荷兰邮政编码,即 4 位数字后跟两个字母。读者可以尝试练习将其扩展到世界上其余的邮政编码。
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"default": "United States of America",
"enum": ["United States of America", "Canada", "Netherlands"]
}
},
"allOf": [
{
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
}
},
{
"if": {
"properties": { "country": { "const": "Canada" } },
"required": ["country"]
},
"then": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
},
{
"if": {
"properties": { "country": { "const": "Netherlands" } },
"required": ["country"]
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
}
}
]
}
// OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"country": "United States of America",
"postal_code": "20500"
}
// OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"postal_code": "20500"
}
// OK
{
"street_address": "24 Sussex Drive",
"country": "Canada",
"postal_code": "K1M 1M4"
}
// OK
{
"street_address": "Adriaan Goekooplaan",
"country": "Netherlands",
"postal_code": "2517 JX"
}
// not OK
{
"street_address": "24 Sussex Drive",
"country": "Canada",
"postal_code": "10000"
}
// not OK
{
"street_address": "1600 Pennsylvania Avenue NW",
"postal_code": "K1M 1M4"
}
笔记 “if”模式中的“required”关键字是必需的,否则如果未定义“country”,则它们都将适用。如果未定义“country”,则将“required”从“United States of America”“IF”模式中删除,使其有效地成为默认值。
笔记 即使“country”是必填字段,仍然建议在每个“if”模式中使用“required”关键字。验证结果将相同,因为“required”将失败,但不包括它会增加错误结果的噪音,因为它将针对所有三个“then”模式验证“postal_code”,导致不相关的错误。
蕴含
在 Draft 7 之前,您可以使用模式组合关键字和称为“蕴含”的布尔代数概念来表达“if-then”条件 。A -> B(A 隐含 B)意味着如果 A 为真,那么 B 也必须为真。它表示为 JSON Schema 可以这样写 !A || B
{
"type": "object",
"properties": {
"restaurantType": { "enum": ["fast-food", "sit-down"] },
"total": { "type": "number" },
"tip": { "type": "number" }
},
"anyOf": [
{
"not": {
"properties": { "restaurantType": { "const": "sit-down" } },
"required": ["restaurantType"]
}
},
{ "required": ["tip"] }
]
}
// OK
{
"restaurantType": "sit-down",
"total": 16.99,
"tip": 3.4
}
// not OK
{
"restaurantType": "sit-down",
"total": 16.99
}
// OK
{
"restaurantType": "fast-food",
"total": 6.99
}
// OK
{ "total": 5.25 }
蕴涵的变化可以用来表达你用if
/ then
/else
关键字表达的内容。 if
/then
可表示为A -> B
,if
/ else
可表示为!A -> B
,if
/ then
/else
可表示为A -> B AND !A -> C
笔记由于此模式不是很直观,因此建议将您在
$defs
中的 条件语句与描述性名称一起, 结合"allOf": [{ "$ref": "#/$defs/sit-down-restaurant-implies-tip-is-required" }]
一起$ref
入您的模式中。