跳到主要内容

有条件地应用子模式

必要依赖

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 之前的版本,dependentRequireddependentSchemas被称为一个关键字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 之前的版本,dependentRequireddependentSchemas被称为一个关键字dependencies。如果依赖值是一个数组,它的行为就像一个 dependentRequired,如果依赖值是一个模式,它的行为就像dependentSchema.

条件语句

新的 Draft7 中 ifthenelse关键字允许基于另一种模式的结果来应用子模式,这很像传统编程语言中的if/ then/else构造。

如果if有效,then也必须有效(并被else忽略)。如果 if无效,else也必须有效(并被then忽略)。

如果thenelse未定义,则if表现为它们的值为true

如果then和/或else出现在没有if,then和 的模式中,else则被忽略。

我们可以把它放在真值表的形式中,显示 when if, then, and elseare valid 的组合 以及整个模式的结果有效性:

ifthenelsewhole schema
TTn/aT
TFn/aF
Fn/aTT
Fn/aFF
n/an/an/aT

例如,假设您想编写一个模式来处理美国和加拿大的地址。这些国家/地区有不同的邮政编码格式,我们希望根据国家/地区选择要验证的格式。如果地址在美国,则该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”关键字没有效果,但将其包含在模式中,对读者比较友好,可以更容易地识别默认行为。

不幸的是,上面的这种方法不能扩展到两个以上的国家。但是,您可以将ifthen包裹在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 -> Bif/ else可表示为!A -> Bif/ then/else可表示为A -> B AND !A -> C

笔记由于此模式不是很直观,因此建议将您在$defs中的 条件语句与描述性名称一起, 结合"allOf": [{ "$ref": "#/$defs/sit-down-restaurant-implies-tip-is-required" }]一起$ref入您的模式中。