How to do JSON Schema multi level conditional Validation with Array Elements - json

I'm working on a JSON schema and I'm stuck on array validations. I have this example input JSON
{
"paths_1": {
"path_1": [
{
"abc": "valid_abc"
},
{
"abc": "invalid_abc"
}
]
},
"paths_2": {
"path_2": [
{
"ghi": "valid_ghi"
}
]
}
}
My rule for this JSON data is, if paths_2.path_2[].ghi is "valid_ghi" and paths_1.path_1[].abc is "valid_abc", then require "def" key for the object that has "valid_abc".
I created this JSON schema for this rule, but it doesn't work as expected.
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"paths_1": {
"type": "object",
"properties": {
"items": {
"properties": {
"path_1": {
"properties": {
"abc": {
"type": "string"
},
"def": {
"type": "string"
}
}
}
}
}
}
},
"paths_2": {
"type": "object",
"properties": {
"items": {
"properties": {
"path_2": {
"properties": {
"ghi": {
"type": "string"
}
}
}
}
}
}
}
},
"allOf": [
{
"if": {
"allOf": [
{
"properties": {
"paths_1": {
"properties": {
"path_1": {
"contains": {
"properties": {
"abc": {
"const": "valid_abc"
}
},
"required": [
"abc"
]
}
}
}
}
}
},
{
"properties": {
"paths_2": {
"properties": {
"path_2": {
"contains": {
"properties": {
"ghi": {
"const": "valid_ghi"
}
},
"required": [
"ghi"
]
}
}
}
}
}
}
]
},
"then": {
"properties": {
"paths_1": {
"properties": {
"path_1": {
"items": {
"required": [
"def"
]
}
}
}
}
}
}
}
]
}
When I tested this schema, it returns 'def' is a required property for the object with "invalid_abc", when it should not.
I tried changing contains keys to items in JSON schema but in this case, if part becomes false and validator returns that input is valid.
Is there a way to validate this input with the given rule?

You need to check for valid_abc in items again.
Your then clause has no context, which I think is what you're expecting.
"items": {
"if": {
"properties": {
"abc": {
"const": "valid_abc"
}
},
"required": [
"abc"
]
},
"then": {
"required": [
"def"
]
}
}
Demo: https://jsonschema.dev/s/M3cvJ
As a result, you can simplify your conditional checking, as you don't need to check if the array contains an object with valid_abc. You can remove if/allOf[0], and unwrap the allOf as it will then only have one subschema.

Related

JSON schema value check on dependencies doesnt work

I really struggle for actually an easy task but I can't get it to work.
I wrote the following JSON Schema:
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"required": [
"root"
],
"properties": {
"root": {
"type": "object",
"required": [
"subobj1"
],
"properties": {
"subobj1": {
"type": "string"
},
"deep2": {
"type": "object",
"properties": {
"d2v1": {
"type": "string"
},
"d2v2": {
"type": "string"
}
}
}
}
},
"if": {
"properties": {
"root": {
"properties": {
"subobj1": {
"const": "moep"
}
}
}
}
},
"then": {
"properties": {
"root": {
"properties": {
"deep2": {
"properties": {
"d2v1": {
"const": "hehe"
}
}
}
}
}
}
},
"else": false
}
}
When I wants to validate it against the following JSON, the validators say it is valid but I'm expecting invalid:
{
"root": {
"subobj1": "moep",
"deep2": {
"d2v1": "hehef"
}
}
I appreciate your help in advance guys. BR

JSON Schema - Required object by value

it is possible to validate required values in a JSON Schema?
I have the following JSON:
{
"genericData": [
{
"name": "field_one",
"value": "data_one"
},
{
"name": "field_two",
"value": [
"array_data_one",
"array_data_two"
]
},
{
"name": "field_three",
"value": {
"attr_one": "some_data",
"attr_two": "more_data"
}
}
]
}
For validating the objects in the array i have the following JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"genericData": {
"type":"array",
"minItems": 2,
"items": [
{
"type": "object",
"if": {
"properties": {
"name": {
"enum": [
"field_one"
]
}
}
},
"then": {
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
}
},
{
"type": "object",
"if": {
"properties": {
"name": {
"enum": [
"field_two"
]
}
}
},
"then": {
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "array",
"minItems": 1,
"items": [
{
"type": "string"
}
]
}
}
}
},
{
"type": "object",
"if": {
"properties": {
"name": {
"enum": [
"field_three"
]
}
}
},
"then": {
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "object",
"properties": {
"attr_one": {
"type": "string"
},
"attr_two": {
"type": "string"
}
},
"required": [
"attr_one",
"attr_two"
]
}
}
}
}
]
}
}
}
Now my question is: Is it possible to set the objects e.g. with the names "field_one" and "field_two" as required? I tried to set the propertie "name", in the "then" clause as required, but this has no impact!
Thanks,
Andreas
I found the solution. The keywords "allOf" and "contains" in combination with "pattern" did what i want:
...
"genericData": {
"type":"array",
"minItems": 2,
"allOf": [
{
"contains": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^field_one$"
}
}
}
},
{
"contains": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^field_two$"
}
}
}
}
],
"items": [
{
"type": "object",
"if": {
"properties": {
"name": {
"enum": [
"field_one"
]
}
}
},
...

Is there a way I can declare a dependency by value for my properties using JSON SCHEMA?

Let's say we have a simple object like:
{
"key_1": "value_1",
"key_2": "value_2"
}
is there a way I can craft my json schema by declaring an inter-key dependency based on their values such that it would NOT ALLOW repetition of value_1 in key_2? I'm using json schema draft-4
DO NOT ALLOW:
{
"key_1": "value_1",
"key_2": "value_1"
}
First of all: if either the keys and/or values are unknown upfront, this falls in the category "business logic" which is out-of-scope for the JSON Schema Specification and would need to be handled independently.
As per your comment, there is a finite number of values (and I assume also of the keys). That means you have a couple of options – even in Draft 4.
1. Using anyOf to ensure that all but one key don't have a particular value and finally wrapping it in allOf (for each value).
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"key_1": { "type": "string" },
"key_2": { "type": "string" },
"key_3": { "type": "string" }
},
"allOf": [
{
"anyOf": [
{
"properties":{
"key_1": { "not": { "enum": ["value_1"] } },
"key_2": { "not": { "enum": ["value_1"] } }
}
},
{
"properties":{
"key_1": { "not": { "enum": ["value_1"] } },
"key_3": { "not": { "enum": ["value_1"] } }
}
},
{
"properties":{
"key_2": { "not": { "enum": ["value_1"] } },
"key_3": { "not": { "enum": ["value_1"] } }
}
}
]
},
{
"anyOf": [
{
"properties":{
"key_1": { "not": { "enum": ["value_2"] } },
"key_2": { "not": { "enum": ["value_2"] } }
}
},
{
"properties":{
"key_1": { "not": { "enum": ["value_2"] } },
"key_3": { "not": { "enum": ["value_2"] } }
}
},
{
"properties":{
"key_2": { "not": { "enum": ["value_2"] } },
"key_3": { "not": { "enum": ["value_2"] } }
}
}
]
}
]
}
2. Using oneOf to indicate that either none or exactly one key has a particular value. Doing that for each possible value and finally wrapping it in allOf.
Beware the required keyword to ensure that the respective key is actually present.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"key_1": { "type": "string" },
"key_2": { "type": "string" },
"key_3": { "type": "string" }
},
"allOf": [
{
"oneOf": [
{
"properties": {
"key_1": { "not": { "enum": ["value_1"] } },
"key_2": { "not": { "enum": ["value_1"] } },
"key_3": { "not": { "enum": ["value_1"] } }
}
},
{
"properties": {
"key_1": { "enum": ["value_1"] }
},
"required": ["key_1"]
},
{
"properties": {
"key_2": { "enum": ["value_1"] }
},
"required": ["key_2"]
},
{
"properties": {
"key_3": { "enum": ["value_1"] }
},
"required": ["key_3"]
}
]
},
{
"oneOf": [
{
"properties": {
"key_1": { "not": { "enum": ["value_2"] } },
"key_2": { "not": { "enum": ["value_2"] } },
"key_3": { "not": { "enum": ["value_2"] } }
}
},
{
"properties": {
"key_1": { "enum": ["value_2"] }
},
"required": ["key_1"]
},
{
"properties": {
"key_2": { "enum": ["value_2"] }
},
"required": ["key_2"]
},
{
"properties": {
"key_3": { "enum": ["value_2"] }
},
"required": ["key_3"]
}
]
}
]
}
There are probably some more combinations, e.g. listing all allowed value combinations, but you get the idea: for a few keys and values this is manageable but it gets unwieldy rather quickly – assuming you have to maintain this manually.

How to simplify multiple JSON-Schema switch-like statements

There is an example of a switch-like condition in the JSON Schema documentation.
https://json-schema.org/understanding-json-schema/reference/conditionals.html
I have added two more countries to the example, which both should have the same postal code patterns as Netherlands. I can get this example to work with two additional if/then structures, but it becomes messy when more items are to be added.
Is there DRYer version, e.g. like the hypothetical one below?
"properties": { "country": { "const": ["Netherlands", "Upperlands", "Lowerlands" } }
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"enum": ["United States of America", "Canada", "Netherlands",
"Upperlands","Lowerlands"]
}
},
"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" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
},
{
"if": {
"properties": { "country": { "const": "Netherlands" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
}
}
]
}
You could use the enum pattern instead. It's less verbose and easier to read, but the error messages you get are terrible, so I suggest you stick with the if/then pattern. Here's what using the enum pattern would look like.
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"enum": ["United States of America", "Canada", "Netherlands",
"Upperlands","Lowerlands"]
}
},
"anyOf": [
{
"properties": {
"country": { "const": "United States of America" },
"postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" }
}
},
{
"properties": {
"country": { "const": "Canada" },
"postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" }
}
},
{
"properties": {
"country": { "const": "Netherlands" },
"postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" }
}
}
]
}
Although there isn't good way around the verbosity, there is something you can do to improve readability/maintainability. You can use definitions to hide the verbose parts.
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"enum": ["United States of America", "Canada", "Netherlands",
"Upperlands","Lowerlands"]
}
},
"allOf": [
{ "$ref": "#/definitions/validate-us-postal-code" },
{ "$ref": "#/definitions/validate-ca-postal-code" },
{ "$ref": "#/definitions/validate-nl-postal-code" }
]
"definitions": {
"validate-us-postal-code": {
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
}
},
"validate-ca-postal-code": {
"if": {
"properties": { "country": { "const": "Canada" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
},
"validate-nl-postal-code": {
"if": {
"properties": { "country": { "const": "Netherlands" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
}
}
}
}
This allows someone to be able to understand everything this schema does just by reading the first few lines. The verbose/complicated stuff is pushed to the bottom where you don't have to deal with it if you don't need to.

Multiple If-Then-Else not validating for JSON Schema

I have a program that I have built that takes a JSON object and produces a JSON schema file based on the details of the input. When I use this program to generate a schema for a smaller JSON object, the schema works correctly and validates as expected. In this smaller schema there is only one if-then-else block.
However when I attempt to generate a schema that makes use of several if-then-else blocks the if-then-else validation seems to stop working at all and will allow anything through.
I'll post an example below to be more clear.
JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"question6-99": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": ["Yes", "No"]
}
}
},
"question6-100": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": ["Mr","Ms","Mrs","Miss","Dr","Rev","Sir","Lady","Lord","Prof", ""]
}
}
}
},
"type": "object",
"properties": {
"form_submission": {
"type": "object",
"properties": {
"sections": {
"type": "object",
"properties": {
"6": {
"type": "object",
"properties": {
"questions": {
"type": "object",
"properties": {
"99": {
"$ref": "#/definitions/question6-99"
},
"100": {
"$ref": "#/definitions/question6-100"
}
},
"if": {
"properties": {
"99": {
"properties": {
"answer": {
"enum": [
"Yes"
]
}
},
"required": [
"answer"
]
}
},
"required": [
"100"
]
},
"then": {
"properties": {
"100": {
"properties": {
"answer": {
"minLength": 1
}
}
}
}
},
"else": {
"properties": {
"100": {
"properties": {
"answer": {
"maxLength": 0
}
}
}
}
}
}
}
}
},
"required": [
"6"
]
}
}
}
}
}
JSON Object being validated
{
"form_submission": {
"sections": {
"1": {
"questions": {
"99": {
"answer": "Yes",
},
"100": {
"answer": "",
}
}
}
}
}
}
For the above example, if the schema is used to validate the object, the answer for question 100 must be answered when question 99 is answered "yes". This works correctly.
However if I then attempt to use the schema below, which uses two if-then-else blocks against the second JSON object, no if-then-else validation occurs.
I'm just wondering if I have done something wrong with the structure of my schema code that is stopping the validation from happening correctly.
Schema using two If-then-else
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"question6-99": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"minLength": 1,
"enum": ["Yes", "No"]
}
}
},
"question6-100": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": ["Mr", "Ms", "Mrs", "Miss", "Dr", "Rev", "Sir", "Lady", "Lord", "Prof", ""]
}
}
},
"question6-101": {
"type": "object",
"properties": {
"answer": {
"type": "string"
}
}
}
},
"type": "object",
"properties": {
"form_submission": {
"type": "object",
"properties": {
"sections": {
"type": "object",
"properties": {
"6": {
"type": "object",
"properties": {
"questions": {
"type": "object",
"properties": {
"99": {
"$ref": "#/definitions/question6-99"
},
"100": {
"$ref": "#/definitions/question6-100"
},
"101": {
"$ref": "#/definitions/question6-101"
}
},
"required": ["99", "100", "101", "102", "103", "104", "105", "111"],
"if": {
"properties": {
"99": {
"properties": {
"answer": {
"enum": ["Yes"]
}
},
"required": ["answer"]
}
},
"required": ["100"]
},
"then": {
"properties": {
"100": {
"properties": {
"answer": {
"minLength": 1
}
}
}
}
},
"else": {
"properties": {
"100": {
"properties": {
"answer": {
"maxLength": 0
}
}
}
}
},
"if": {
"properties": {
"99": {
"properties": {
"answer": {
"enum": ["Yes"]
}
},
"required": ["answer"]
}
},
"required": ["101"]
},
"then": {
"properties": {
"101": {
"properties": {
"answer": {
"minLength": 1
}
}
}
}
},
"else": {
"properties": {
"101": {
"properties": {
"answer": {
"maxLength": 0
}
}
}
}
}
}
}
}
},
"required": ["1"]
}
}
}
}
}
Second schema to validate
{
"form_submission": {
"id": "80035",
"status": "Incomplete",
"validated": true,
"failure_reason": "",
"sections": {
"1": {
"questions": {
"99": {
"answer": "Yes",
"web_validated": true,
"web_error_string": "",
"server_error_string": ""
},
"100": {
"answer": "",
"web_validated": true,
"web_error_string": "",
"server_error_string": ""
},
"101": {
"answer": "Yes",
"web_validated": true,
"web_error_string": "",
"server_error_string": ""
}
},
"name": "",
"validated": true,
"server_validated": true,
"notes": ""
}
},
"submitted_section_id": 11
}
}
Added allOf to Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"question6-99": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"minLength": 1,
"enum": ["Yes", "No"]
}
}
},
"question6-100": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": ["Mr", "Ms", "Mrs", "Miss", "Dr", "Rev", "Sir", "Lady", "Lord", "Prof", ""]
}
}
},
"question6-101": {
"type": "object",
"properties": {
"answer": {
"type": "string"
}
}
}
},
"type": "object",
"properties": {
"form_submission": {
"type": "object",
"properties": {
"sections": {
"type": "object",
"properties": {
"6": {
"type": "object",
"properties": {
"questions": {
"type": "object",
"properties": {
"99": {
"$ref": "#/definitions/question6-99"
},
"100": {
"$ref": "#/definitions/question6-100"
},
"101": {
"$ref": "#/definitions/question6-101"
}
},
"required": ["99", "100", "101", "102", "103", "104", "105", "111"],
"allOf": [
{
"if": {
"properties": {
"99": {
"properties": {
"answer": {
"enum": ["Yes"]
}
},
"required": ["answer"]
}
},
"required": ["100"]
},
"then": {
"properties": {
"100": {
"properties": {
"answer": {
"minLength": 1
}
}
}
}
},
"else": {
"properties": {
"100": {
"properties": {
"answer": {
"maxLength": 0
}
}
}
}
}},
{
"if": {
"properties": {
"99": {
"properties": {
"answer": {
"enum": ["Yes"]
}
},
"required": ["answer"]
}
},
"required": ["101"]
},
"then": {
"properties": {
"101": {
"properties": {
"answer": {
"minLength": 1
}
}
}
}
},
"else": {
"properties": {
"101": {
"properties": {
"answer": {
"maxLength": 0
}
}
}
}
}
}
]
}
}
}
},
"required": ["1"]
}
}
}
}
}
If we remove the complicated bits, I think the problem becomes clear.
{
"if": { ... },
"then": { ... },
"if": { ... },
"then": { ... }
}
In JSON the value of duplicated keys is undefined. One of these ifs and one of these thens will be ignored by a JSON parser.
You can get around this problem by wrapping your ifs in an allOf.
{
"allOf": [
{
"if": { ... },
"then": { ... }
},
{
"if": { ... },
"then": { ... }
}
]
}
It's good practice to always wrap your if/then/else in allOf even if you have only one. This is because a JSON object is by definition unordered. Therefore, some tool might rearrange your keywords splitting up the ifs and thens in a way that makes the schema difficult to decipher.
{
"definitions": {},
"else": {},
"if": {},
"properties": {},
"then": {},
"type": "object",
}