Yuk, upward dependencies can be really unpleaseant. Does the model have to be shaped that way?
Solution
You are on the right track. What you are missing is checking correctly if at least one element of "Fields" array had that "SW Large" : true and then shape the proper dependency.
Since draft-06 it is solved with "contains" keyword. In order to not repeat content, I'd recommend to read following:
JSON Schema: How to check that an array contains at least one object with a property with a given value?
Json Schema: Require a property only when a specific property is present in a deep nested object (very educational!)
https://json-schema.org/understanding-json-schema/reference/array.html
Your schema re-worked below. See the "definitions" section
First of all it's adding "contains" : { schema } to "Fields" definition. I need it as separate schema in order to use it as a condition in logical implication.
"definitions" : {
"Fields-contains-at-least-1-element-with-SW-Large-true" : {
"properties": {
"Fields" : {
"contains" : {
"properties": {
"SW Large": { "enum": [true] }
},
"required" : ["SW Large"]
}
}
},
}
},
If you would add it permanently, it might look like:
"Fields" : {
"type" : "array",
"contains" : {
"properties": {
"SW Large": { "enum": [true] }
},
"required" : ["SW Large"]
}
"items": {...},
}
which translates to "at least one item of "Fields" array must contain object with said property name and said value". Every JSON with "Fields" array that contains no item with "SW Large" : true would fail validation against such schema. And if you reverse the "contains" schema definition like:
"Fields" : {
"type" : "array",
"contains" : {
"not" : {
"properties": {
"SW Large": { "enum": [true] }
},
"required" : ["SW Large"]
}
}
"items": {...},
}
it would translate to "no item of "Fields" array can contain object with said property name and said value". Every JSON with "Fields" array that contains at least one item with "SW Large" : true would fail validation against such schema.
I don't want any of above to happen. I want to link "Fields/contains" condition with requiring or not requiring the "SW Words" property one level above, hence getting schema excluded to "definitions" section and making a proper use of it.
The "upward-dependency" is defined using that check and proper logical implication with "anyOf" keyword
It doesn't contain at least 1 item with "SW Large" : true OR "SW Words" is required
"definitions" : {
"upward-dependency" : {
"anyOf" : [
{ "not" : {"$ref" : "#/definitions/Fields-contains-at-least-1-element-with-SW-Large-true"} },
{ "required" : ["SW Words"] }
]
},
},
At the level of single item of "Registers" array I've added the "dependencies". Whenever "Fields" item appear in one of "Registers" items, it's been checked if it contains the unfortunate "SW Large" : true and if it does - the "SW Words" becomes required. Voila!
"items" : {
"$id": "#/properties/Registers/items
"type" : "object",
"properties" : {
"Fields": {
"$id": "#/properties/Registers/items/properties/Fields",
"type": "array",
"items": {
"$id": "#/properties/Registers/items/properties/Fields/items",
"type": "object",
"propertyNames" : {
"enum" : [
"Name",
"SW Large"
]
},
"required": [
"Name"
],
"properties": {
"Name": {
"$id": "#/properties/Registers/items/properties/Fields/items/properties/Name",
"type": "string"
},
"SW Large": {
"$id": "#/properties/Registers/items/properties/Fields/items/properties/SW Large",
"type": "boolean"
}
}
}
}
},
"dependencies" : {
"Fields" : { "$ref" : "#/definitions/upward-dependency" }
},
}
I've checked Schema with online validator and added your object to "examples" section.
Full schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"propertyNames" : {
"enum" : [
"Registers"
]
},
"required": [
"Registers"
],
"examples" : [
{
"Registers": [
{
"Name": "device",
"Address": "100",
"SW Words": 2,
"Fields": [
{
"Name" : "Product",
"SW Large" : true
},
{
"Name": "Version"
}
]
}
]
}
],
"properties": {
"Registers": {
"$id": "#/properties/Registers",
"type": "array",
"items": {
"$id": "#/properties/Registers/items",
"type": "object",
"propertyNames" : {
"enum" : [
"Name",
"Address",
"SW Words",
"Fields"
]
},
"required": [
"Name",
"Address",
"Fields"
],
"properties": {
"Name": {
"$id": "#/properties/Registers/items/properties/Name",
"type": "string"
},
"Address": {
"$id": "#/properties/Registers/items/properties/Address",
"type": "string"
},
"SW Words": {
"$id": "#/properties/Sections/items/properties/Blocks/items/properties/SW Words",
"type": "integer",
"default": 2,
"minimum": 2
},
"Fields": {
"$id": "#/properties/Registers/items/properties/Fields",
"type": "array",
"items": {
"$id": "#/properties/Registers/items/properties/Fields/items",
"type": "object",
"propertyNames" : {
"enum" : [
"Name",
"SW Large"
]
},
"required": [
"Name"
],
"properties": {
"Name": {
"type": "string"
},
"SW Large": {
"type": "boolean"
}
}
}
}
},
"dependencies" : {
"Fields" : { "$ref" : "#/definitions/upward-dependency" }
},
}
}
},
"definitions" : {
"upward-dependency" : {
"anyOf" : [
{ "not" : {"$ref" : "#/definitions/Fields-contains-at-least-1-element-with-SW-Large-true"} },
{ "required" : ["SW Words"] }
]
},
"Fields-contains-at-least-1-element-with-SW-Large-true" : {
"properties": {
"Fields" : {
"contains" : {
"properties": {
"SW Large": { "enum": [true] }
},
"required" : ["SW Large"]
}
}
},
}
},
}
Some side notes:
Instead of "additionalProperties":false please use "propertyNames". It's been specifically introduced for purpose of making sure only demanded properties are present in JSON object validated aginst schema.
"propertyNames" : {
"enum" : [
"Registers"
]
}
See: https://json-schema.org/understanding-json-schema/reference/object.html#property-names
It is very important to properly shape schemas/sub-schemas depending on which level these should be applied. Please note how "#/definitions/Fields-contains-at-least-1-element-with-SW-Large-true" properly reflects schema at "#/Registers/items" nesting level (since the "dependencies" is applied to an object on "#/Registers/items" level and one can think of it as of ""#/Registers/items/dependencies"). See https://json-schema.org/understanding-json-schema/reference/object.html#dependencies .
If you intend to validate single item of "Fields" array separately or even single item of "Registers" array separately, you might consider reshaping your schema to separate sub-schemas like I did with "questionA" in here: https://stackoverflow.com/a/53309856/2811843 . Thus - if necessity is there - you could easily exclude sub-schemas from main schema and just properly reference to them using "$ref" keyword. Some reading on JSON Pointer and relative JSON Pointer might be also useful for structuring complex schemas.
Worth to start at: https://json-schema.org/understanding-json-schema/structuring.html
I hope it helped.
Answer from PsychoFish on Stack Overflowjsonschema - JSON schema conditional dependency on value - Stack Overflow
Proposal: Add propertyDependencies keyword (aka discriminator)
dependencies - JSON Schema - specify field is required based on value of another field - Stack Overflow
Is it possible to have "dependencies" based on values instead of presence?
You can do this with a boolean logic concept called implication (!A or B). It can be used like an "if-then" statement. For example, either "color" is not "red" or "redQuote" is required. Any time I need to use this, I break it down with definitions so it reads as nice as possible.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"colour": { "enum": ["red", "black", "blue"] },
"blackQuote": { "type": "string", "maxLength": 11 },
"redQuote": { "type": "string", "maxLength": 11 }
},
"allOf": [
{ "$ref": "#/definitions/red-requires-redQuote" },
{ "$ref": "#/definitions/black-requires-blackQuote" }
],
"required": ["colour"],
"definitions": {
"red-requires-redQuote": {
"anyOf": [
{ "not": { "$ref": "#/definitions/is-red" } },
{ "required": ["redQuote"] }
]
},
"black-requires-blackQuote": {
"anyOf": [
{ "not": { "$ref": "#/definitions/is-black" } },
{ "required": ["blackQuote"] }
]
},
"is-red": {
"properties": {
"colour": { "enum": ["red"] }
},
"required": ["colour"]
},
"is-black": {
"properties": {
"colour": { "enum": ["black"] }
},
"required": ["colour"]
}
}
}
Simplest answer in draft-04 (as noted by Ganesh in a comment):
{
"definitions": {},
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"colour": {
"type": "string",
"enum": ["red", "black", "blue"]
},
"blackQuote": {
"type": "string",
"maxLength": 11
},
"redQuote": {
"type": "string",
"maxLength": 11
}
},
"oneOf": [
{
"properties": {
"colour": {"enum": ["red"]}
},
"required": ["redQuote"]
},
{
"properties": {
"colour": {"enum": ["black"]}
},
"required": ["blackQuote"]
},
{
"properties": {
"colour": {"enum": ["blue"]}
}
}
],
"required": [
"colour"
]
}
This is definitely possible with version 3 of the draft. Since you have a complete list of allowed countries, then you could do something like this:
{
"type": [
{
"title": "New Zealand (no postcode)",
"type": "object",
"properties": {
"country": {"enum": ["NZ", "NZL", "NEW ZEALAND"]}
}
},
{
"title": "Other countries (require postcode)",
"type": "object",
"properties": {
"country": {"enum": [<all the other countries>]},
"postcode": {"required": true}
}
}
],
"properties": {
"country": {
"type" : "string",
"default" : "AUS"
},
"postcode": {
"type" : "string"
}
}
}
So you actually define two sub-types for your schema, one for countries that require a postcode, and one for countries that do not.
EDIT - the v4 equivalent is extremely similar. Simply rename the top-level "type" array to "oneOf".
If anybody is looking for a solution for draft 4 you can use dependencies keyword together with a enum keyword:
{
"type": "object",
"properties": {
"play": {
"type": "boolean"
},
"play-options": {
"type": "string"
}
},
"dependencies": {
"play-options": {
"properties": {
"play": {
"enum": [true]
}
}
}
}
}
In this wayplay-options will always require play value to be true.
Depending on your situation, there are a few different approaches. I can think of four different ways to conditionally require a field.
Dependencies
The dependentSchemas keyword is a conditional way to apply a schema. Foreach property in dependentSchemas, if the property is present in the JSON being validated, then the schema associated with that key must also be valid. If the "foo" property is present, then the "bar" property is required
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"dependentSchemas": {
"foo": { "required": ["bar"] }
}
}
If all the dependent schema needs is the required keyword, you can use the dependentRequired keyword as a shorthand. The following has the same effect as the previous example.
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"dependentRequired": {
"foo": ["bar"]
}
}
NOTE: In draft-07 and below these were one keyword called dependencies. If the value is a schema it behaved like dependentSchemas. If the value is an array, it behaved like dependentRequired.
Implication
If your condition depends on the value of a field, you can use a boolean logic concept called implication. "A implies B" effectively means, if A is true then B must also be true. Implication can also be expressed as "!A or B". Either the "foo" property does not equal "bar", or the "bar" property is required. Or, in other words: If the "foo" property equals "bar", Then the "bar" property is required
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"anyOf": [
{
"not": {
"properties": {
"foo": { "const": "bar" }
},
"required": ["foo"]
}
},
{ "required": ["bar"] }
]
}
If "foo" is not equal to "bar", #/anyOf/0 matches and validation succeeds. If "foo" equals "bar", #/anyOf/0 fails and #/anyOf/1 must be valid for the anyOf validation to be successful.
NOTE: The if/then keywords have the same behavior, but are easier to read and maintain. It's recommended to only use this approach if you are using an older version of JSON Schema that doesn't support if/then.
Enum
If your conditional is based on an enum, it's a little more straight forward. "foo" can be "bar" or "baz". If "foo" equals "bar", then "bar" is required. If "foo" equals "baz", then "baz" is required.
{
"type": "object",
"properties": {
"foo": { "enum": ["bar", "baz"] },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"anyOf": [
{
"properties": {
"foo": { "const": "bar" }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "const": "baz" }
},
"required": ["baz"]
}
]
}
NOTE: This approach is not recommended because it can produce confusing error messages. The if/then keywords are generally a better approach.
If-Then-Else
The if, then and else keywords are shorthand for the implication pattern described above. These keywords were added in draft-07. If the "foo" property equals "bar", Then the "bar" property is required
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"if": {
"properties": {
"foo": { "const": "bar" }
},
"required": ["foo"]
},
"then": { "required": ["bar"] }
}
EDIT 12/23/2017: Implication section updated and If-Then-Else section added.
EDIT 06/04/2018: Bugfix for If-Then-Else and update singleton enums to use const.
EDIT 07/06/2022: Update Dependencies section to use the new dependentSchemas/dependentRequired keywords instead of dependencies.
As of 2022, dependencies has been deprecated, and split into dependentRequired (see e.g. this example) and dependentSchemas (see e.g. this example). Just using dependentRequired solves the issue:
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"dependentRequired": {
"foo": ["bar"]
}
}