リファレンス

サブスキーマを条件付きで適用する

dependentRequired

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"] }}
データ
{ "name": "John Doe", "credit_card": 5555555555555555, "billing_address": "555 Debtor's Lane"}
スキーマに準拠しています

このインスタンスは、credit_cardを持っていますが、billing_addressがありません。

データ
{ "name": "John Doe", "credit_card": 5555555555555555}
スキーマに準拠していません

これは、credit_cardbilling_addressもないため、問題ありません。

データ
{ "name": "John Doe"}
スキーマに準拠しています

依存関係は双方向ではないことに注意してください。クレジットカード番号なしで請求先住所を持つことは問題ありません。

データ
{ "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"] }}

このインスタンスにはcredit_cardがありますが、billing_addressが欠落しています。

データ
{ "name": "John Doe", "credit_card": 5555555555555555}
スキーマに準拠していません

これにはbilling_addressがありますが、credit_cardが欠落しています。

データ
{ "name": "John Doe", "billing_address": "555 Debtor's Lane"}
スキーマに準拠していません
ドラフト固有の情報
以前のドラフト2019-09では、dependentRequireddependentSchemasは、dependenciesという1つのキーワードでした。依存関係の値が配列の場合、dependentRequiredのように動作し、依存関係の値がスキーマの場合、dependentSchemaのように動作していました。

dependentSchemas

dependentSchemas キーワードは、特定のプロパティが存在する場合に、サブスキーマを条件付きで適用します。このスキーマは、allOf がスキーマを適用するのと同じ方法で適用されます。何もマージまたは拡張されません。両方のスキーマが独立して適用されます。

たとえば、上記の別の書き方としては、次のようになります。

スキーマ
{ "type": "object", "properties": { "name": { "type": "string" }, "credit_card": { "type": "number" } }, "required": ["name"], "dependentSchemas": { "credit_card": { "properties": { "billing_address": { "type": "string" } }, "required": ["billing_address"] } }}
データ
{ "name": "John Doe", "credit_card": 5555555555555555, "billing_address": "555 Debtor's Lane"}
スキーマに準拠しています

このインスタンスには credit_card がありますが、billing_address が欠落しています。

データ
{ "name": "John Doe", "credit_card": 5555555555555555}
スキーマに準拠していません

こちらには billing_address がありますが、credit_card が欠落しています。ここでは billing_address は追加のプロパティのように見えるため、これはパスします。

データ
{ "name": "John Doe", "billing_address": "555 Debtor's Lane"}
スキーマに準拠しています
ドラフト固有の情報
以前のドラフト2019-09では、dependentRequireddependentSchemasは、dependenciesという1つのキーワードでした。依存関係の値が配列の場合、dependentRequiredのように動作し、依存関係の値がスキーマの場合、dependentSchemaのように動作していました。

If-Then-Else

ドラフト 7 で新登場

ifthen、および else キーワードを使用すると、別のスキーマの結果に基づいてサブスキーマを適用できます。これは、従来のプログラミング言語でよく見られる if / then / else 構文によく似ています。

if が有効な場合、then も有効である必要があります(そして else は無視されます)。if が無効な場合、else も有効である必要があります(そして then は無視されます)。

then または else が定義されていない場合、if は値が true であるかのように動作します。

then または elseif なしでスキーマに表示される場合、thenelse は無視されます。

これを真理値表の形式で表現すると、ifthen、および else が有効な場合の組み合わせと、スキーマ全体の有効性の結果を示します。

ifthenelseスキーマ全体
TTn/aT
TFn/aF
Fn/aTT
Fn/aFF
n/an/an/aT

たとえば、アメリカとカナダの住所を処理するスキーマを作成するとします。これらの国では郵便番号の形式が異なり、国に基づいて検証する形式を選択する必要があります。住所がアメリカの場合、postal_code フィールドは「zipcode」で、5桁の数字の後にオプションで4桁のサフィックスが続きます。住所がカナダの場合、postal_code フィールドは、文字と数字が交互に配置された6桁の英数字文字列です。

スキーマ
{ "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]" } } }}
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "country": "United States of America", "postal_code": "20500"}
スキーマに準拠しています
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "20500"}
スキーマに準拠しています
データ
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "K1M 1M4"}
スキーマに準拠しています
データ
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "10000"}
スキーマに準拠していません
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "K1M 1M4"}
スキーマに準拠していません

この例では、"country" は必須プロパティではありません。 if スキーマも "country" プロパティを必須としていないため、これはパスし、"then" スキーマが適用されます。したがって、"country" プロパティが定義されていない場合、デフォルトの動作は "postal_code" を米国の郵便番号として検証することです。"default" キーワードは効果はありませんが、スキーマの読者がデフォルトの動作をより簡単に認識できるように含めることが推奨されます。

残念ながら、上記のアプローチは2つ以上の国にはスケールしません。ただし、ifthen のペアを allOf の中にラップして、スケールするものを生成できます。この例では、アメリカとカナダの郵便番号を使用しますが、オランダの郵便番号(4桁の数字と2文字の組み合わせ)も追加します。残りの世界の郵便番号にこれを拡張するのは読者の課題とします。

スキーマ
{ "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}" } } } } ]}
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "country": "United States of America", "postal_code": "20500"}
スキーマに準拠しています
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "20500"}
スキーマに準拠しています
データ
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "K1M 1M4"}
スキーマに準拠しています
データ
{ "street_address": "Adriaan Goekooplaan", "country": "Netherlands", "postal_code": "2517 JX"}
スキーマに準拠しています
データ
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "10000"}
スキーマに準拠していません
データ
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "K1M 1M4"}
スキーマに準拠していません

required キーワードは、if スキーマで必須です。そうしないと、「country」が定義されていない場合に、すべてが適用されてしまいます。「アメリカ合衆国」のif スキーマからrequired を外すと、「country」が定義されていない場合、事実上デフォルトになります。

「country」が必須フィールドであったとしても、各if スキーマにrequired キーワードを含めることをお勧めします。検証結果は同じになります。これは、required が失敗するためですが、それを含めないと、「postal_code」を3つのthen スキーマすべてに対して検証し、無関係なエラーにつながるため、エラー結果にノイズが加わる可能性があります。

含意

Draft 7 より前は、「if-then」条件をスキーマ合成キーワードと「含意」と呼ばれるブール代数の概念を使用して表現できます。 A -> B (A は B を含意すると発音)は、A が真の場合、B も真でなければならないことを意味します。これは、!A || B として表現でき、JSON スキーマとして表現できます。

スキーマ
{ "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"] } ]}
データ
{ "restaurantType": "sit-down", "total": 16.99, "tip": 3.4}
スキーマに準拠しています
データ
{ "restaurantType": "sit-down", "total": 16.99}
スキーマに準拠していません
データ
{ "restaurantType": "fast-food", "total": 6.99}
スキーマに準拠しています
データ
{ "total": 5.25 }
スキーマに準拠しています

含意のバリエーションは、if / then / else キーワードで表現できるものと同じものを表現するために使用できます。if / thenA -> B と表現でき、if / else!A -> B と表現でき、if / then / elseA -> B AND !A -> C と表現できます。

このパターンはあまり直感的ではないため、条件式は記述的な名前を持つ $defs に入れ、"allOf": [{ "$ref": "#/$defs/sit-down-restaurant-implies-tip-is-required" }] を使用してスキーマに $ref で参照することをお勧めします。

お困りですか?

このドキュメントは役に立ちましたか?

ドキュメントを改善にご協力ください!

JSON Schema では、ドキュメントへの貢献は他の種類の貢献と同じくらい重要だと考えています!

まだお困りですか?

JSON Schema を学ぶのはしばしば混乱しますが、ご心配なく、私たちがお手伝いします!