Related
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"
]
}
}
},
...
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.
Here is my JSON schema and JSON as shown below at the bottom and using ajv validator to support json spec draft 7.
By default, the 'science' object must be represented as:
//Default science object
{"type": "science", "rule": {"sciencePattern": {}}}
where the 'rule' and 'sciencePattern' MUST be there.
However, if the 'sciencePattern' about to contains other attributes (as per the schema), then the below validation should kick in:
If the default science object is present, then the "scored" attributes in "arts" object should be REQUIRED.
If the NESTED "scored" array attribute is present within rule as:
{ "type": "science", "rule":{"sciencePattern":{"marks":{"scored":[10]}}} }
then the "scored" attributes in "arts" object should not be REQUIRED. In other words, if some one specify "scored" attribute withn the "arts" object, the schema validation should complain as there is a "scored" attribute is available in "science" object.
//JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"exam"
],
"properties": {
"exam": {
"type": "array",
"minItems": 1,
"items": {
"anyOf": [
{
"$ref": "#/definitions/science"
},
{
"$ref": "#/definitions/arts"
}
]
}
},
"if": {
"type": "object",
"required": [
"type",
"rule"
],
"properties": {
"type": {
"const": "science"
},
"rule": {
"type": "object",
"required": [
"sciencePattern"
],
"properties": {
"sciencePattern": {
"$ref": "#/definitions/sciencePattern"
}
}
}
}
},
"then": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"const": "arts"
},
"not": {
"required": [
"scored"
]
}
}
}
},
"definitions": {
"sciencePattern": {
"type": "object",
"required": [
"marks"
],
"properties": {
"marks": {
"type": "object",
"required": [
"scored"
],
"properties": {
"scored": {
"type": "array"
}
}
}
}
},
"science": {
"type": "object",
"properties": {
"type": {
"const": "science"
},
"rule": {
"required": [
"sciencePattern"
],
"properties": {
"sciencePattern": {
"$ref": "#/definitions/sciencePattern"
}
}
}
}
},
"arts": {
"required": [
"scored"
],
"properties": {
"type": {
"const": "arts"
},
"scored": {
"type": "number"
},
"remarks": {
"type": "string"
}
}
}
}
}
and My JSON
{
"exam": [
{
"type": "science",
"rule": {
"sciencePattern": {
"marks": {
"scored": [10]
}
}
}
},
{
"type": "arts",
"scored": 10 //This should complain as 'scored' is available above in science
}
]
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"exam"
],
"properties": {
"exam": {
"type": "array",
"allOf":[
{
"if": {
"contains": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"const": "science"
}
}
}
},
"then":{
"required":["rule", "sciencePattern"]
}
},
{
"if": {
"contains": {
"type": "object",
"required":["type","rule", "sciencePattern"],
"properties": {
"type": {
"const": "science"
}
}
}
},
"then":{
"required":["scored"]
}
},
{
"if": {
"contains": {
"type": "object",
"required": [
"type",
"rule"
],
"properties": {
"type": {
"const": "science"
},
"rule": {
"type": "object",
"required": [
"sciencePattern"
],
"properties": {
"sciencePattern": {
"$ref": "#/definitions/emptySciencePattern"
}
}
}
}
}
},
"then": {
"contains": {
"type": "object",
"required": ["type", "scored"],
"properties": {
"type": {
"const": "arts"
}
}
}
},
"else": {
"if": {
"contains": {
"type": "object",
"required": [
"type",
"rule"
],
"properties": {
"type": {
"const": "science"
},
"rule": {
"type": "object",
"required": [
"sciencePattern"
],
"properties": {
"sciencePattern": {
"$ref": "#/definitions/sciencePattern"
}
}
}
}
}
},
"then": {
"contains": {
"required": [
"type"
],
"properties": {
"type": {
"const": "arts"
}
},
"not": {
"required": ["scored"]
}
}
}
}
}],
"minItems": 1,
"items": {
"anyOf": [{
"$ref": "#/definitions/science"
}, {
"$ref": "#/definitions/arts"
}]
}
}
},
"definitions": {
"rule": {
"type": "object",
"required": ["sciencePattern"],
"properties": {
"sciencePattern": {
"$ref": "#/definitions/sciencePattern"
}
}
},
"emptySciencePattern": {
"type": "object",
"maxProperties": 0,
"additionalProperties": false,
"properties": {}
},
"sciencePattern": {
"type": "object",
"properties": {
"marks": {
"type": "object",
"properties": {
"scored": {
"type": "array"
}
}
}
}
},
"science": {
"type": "object",
"required": ["rule"],
"properties": {
"type": {
"const": "science"
},
"rule": {
"$ref":"#/definitions/rule"
}
}
},
"arts": {
"properties": {
"type": {
"const": "arts"
},
"scored": {
"type": "number"
},
"remarks": {
"type": "string"
}
}
}
}
}
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.
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",
}