Json schema permits override of fields in objects in array - json

I have an json that looks like this
"List": {
{"Color": "red"},
{}
},
"Color": "grey"
}
whereas it means that the default color is grey, and the object in the list could override this Color.
The schema should allow the json to pass as long as default color(the property in the same level of List) is present. If not, it shall only allow the json to pass the schema check if all items in the list have specified a "Color".
May I know how can I write a json schema that does this check? I am aware of anyOf but I don't think it can check for all items in the array.
I tried
{
"type": "object",
"properties": {
"List": {"type": "array", "items": {"$ref:" "#/definitions/Item"}},
"Color": {"type": "string"}
},
"definitions": {
"Item": {"type: "object", "properties": " {"Color": {"type": "string"}}}
},
"anyOf": {
{
"type": "object",
"required": ["Color"]
},
{
"type": "object",
"List": {
"type": "array",
"items": {"$ref": "#/definitions/Item", "required": ["Color"]}
}
}
}
But it does not seem that the required color for the anyOf[1] is picked up by the validator.
Please help.! Thank you.

The schema in the other answer is correct, but is unnecessarily complicated. Here's an example that removes duplication and make the schema easier to read.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"List": {
"type": "array",
"items": {
"type": "object",
"properties": {
"Color": { "type": "string" },
"Shape": { "type": "string" }
}
}
},
"Color": { "type": "string" },
"Shape": { "type": "string" }
},
"required": ["List"],
"allOf": [
{ "$ref": "#/definitions/color-required-if-no-default-color" },
{ "$ref": "#/definitions/shape-required-if-no-default-shape" }
],
"definitions": {
"color-required-if-no-default-color": {
"anyOf": [
{ "required": ["Color"] },
{
"properties": {
"List": {
"items": { "required": ["Color"] }
}
}
}
]
},
"shape-required-if-no-default-shape": {
"anyOf": [
{ "required": ["Shape"] },
{
"properties": {
"List": {
"items": { "required": ["Shape"] }
}
}
}
]
}
}
}

I think I found out a way:
Schema is like below
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"Color": {"type":"string"}, "Shape": {"type": "string"}},
"allOf":[
{
"anyOf": [
{
"required": ["Color"],
"properties": {"List": {"type": "array", "items": {"$ref": "#/definitions/Item"}}}
},
{
"properties": {"List": {"type": "array", "items": {"$ref": "#/definitions/ColorItem"}}}
}
]
},
{
"anyOf": [
{
"required": ["Shape"],
"properties": {"List": {"type": "array", "items": {"$ref": "#/definitions/Item"}}}
},
{
"properties": {"List": {"type": "array", "items": {"$ref": "#/definitions/ShapeItem"}}}
}
]
}
],
"required": ["List"],
"definitions": {
"Item": {
"type": "object",
"properties": {}
},
"ColorItem": {
"allOf": [{"$ref": "#/definitions/Item"},
{"properties": {"Color": {"type": "string"}}, "required": ["Color"]}]
},
"ShapeItem": {
"allOf": [{"$ref": "#/definitions/Item"},
{"properties": {"Shape": {"type": "string"}}, "required": ["Shape"]}]
}
}
}
Basically this does what I want. So it will pass the json only if either there is a Color/Shape read from top-level json, or we can find it in the array.

Related

JSON Schema oneOf for array options

I am trying to model the scenario where the JSON payload has a "steps" array and it's contents may be only one of predefined a set of options, for example:
{ "steps": ["s0"] }
or
{ "steps": ["s1"] }
or
{ "steps": ["s0", "s2"] }
How would I model this in a schema? The following:
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Payload",
"type": "object",
"properties": {
"steps": {
"type": "array",
"oneOf": [
["s0"],
["s1"],
["s0", "s2"]
]
}
}
}
fails with "Unexpected token when reading schemas: StartArray. Path 'properties.steps.oneOf[0]'"
EDIT
Apologies for moving the goalposts, I simplified my problem to a point where the proposed solutions work for the simplified
version but not the original. The extra complication is that instead of string values I need to $ref objects sooo...
Sample inputs:
{ "steps": [{"name": "S0"}] }
or
{ "steps": [{"name": "S1"}] }
or
{ "steps": [{"name": "S1"}, {"name": "S2"}] }
A schema (following suggestion by #EylM) that doesn't match as expected
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Request",
"type": "object",
"properties": {
"steps": {
"type": "array",
"oneOf": [
{
"const": [
{"$ref": "#/definitions/s0"}
]
},
{
"const": [
{"$ref": "#/definitions/s1"}
]
},
{
"const": [
{"$ref": "#/definitions/s1"},
{"$ref": "#/definitions/s2"}
]
}
]
}
},
"required": [
"steps"
],
"definitions": {
"s0": {
"type": "object",
"properties": {"name": { "const": "S0" }}
},
"s1": {
"type": "object",
"properties": {"name": {"const": "S1" }}
},
"s2": {
"type": "object",
"properties": {"name": { "const": "S2" }}
}
}
}
With this schema and input { "steps":[{"name": "s0"}] } I get
JSON is valid against no schemas from 'oneOf'
For whatever it's worth, I am using https://www.jsonschemavalidator.net/ for experimenting.
Updated my answer according to your edit. Following JSON schema validates all 3 conditions in the question and I assumed { "steps": [{"name": "S2"}, {"name": "S1"}] } is not valid. Correct me if I'm wrong.
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Request",
"type": "object",
"properties": {
"steps": {
"type": "array",
"anyOf": [
{
"maxItems": 1,
"items": {
"oneOf": [
{
"$ref": "#/definitions/s0"
},
{
"$ref": "#/definitions/s1"
}
]
}
},
{
"minItems": 2,
"maxItems": 2,
"items": [
{
"$ref": "#/definitions/s1"
},
{
"$ref": "#/definitions/s2"
}
]
}
]
}
},
"required": [
"steps"
],
"definitions": {
"s0": {
"type": "object",
"properties": {
"name": {
"const": "S0"
}
}
},
"s1": {
"type": "object",
"properties": {
"name": {
"const": "S1"
}
}
},
"s2": {
"type": "object",
"properties": {
"name": {
"const": "S2"
}
}
}
}
}
In case you want to pass validation for { "steps": [{"name": "S2"}, {"name": "S1"}] } use another anyOf block as follows.
{
"minItems": 2,
"maxItems": 2,
"items": [
{
"$ref": "#/definitions/s2"
},
{
"$ref": "#/definitions/s1"
}
]
}
Try using enum keyword.
The enum keyword is used to restrict a value to a fixed set of values.
It must be an array with at least one element, where each element is
unique.
json-schema - More info.
In your case, JSON would look like:
{
"$id": "https://example.com/person.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Payload",
"type": "object",
"properties": {
"steps": {
"type": "array",
"oneOf": [
{"enum": ["s1"]},
{"enum": ["s0"]},
{"enum": ["s1, s2"]}
]
}
}
}

Conditional JSON schema based on dynamic property value

I'm trying to validate a JSON file that contains several nested components of the same type. Each component has a class string property. I'm trying to apply different validation schemas from my definitions to each component based on the value of class. Furthermore, the values of class can be "button-open", "button-close", "icon-message", "icon-...", "container", etc and I would like to apply the same validation schema for all the "buttons-" another to "icons-" and another to the rest.
The code I provide is what I have tried. I have excluded icons- for the shake of simplicity. As you can see I tried to match with a regular expression whenever there is button in class. Then it should constraint the minimum value of children elements to 1.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["results"],
"properties": {
"results": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"anyOf": [
{
"$ref": "#/definitions/genericComponent"
},
{
"$ref": "#/definitions/button"
}
]
}
}
},
"definitions": {
"genericComponent": {
"type": "object",
"$id": "#/definitions/genericComponent",
"required":[
"class",
"children"
],
"properties":{
"class": {
"type": "string",
"description": "predicted class of the element(img, icon..)"
},
"children":{
"type": "array",
"description": "this element can contain more of the same type",
"items":{
"type": "object",
"anyOf":[
{"$ref": "#/definitions/button"},
{"$ref": "#/definitions/genericComponent"}
]
}
}
}
},
"button": {
"$id": "#/definitions/button",
"allOf":[
{"$ref": "#/definitions/genericComponent"},
{"properties": {
"class":{
"type":"string",
"pattern": "(button)"
},
"children":{
"type": "array",
"description": "this element must contain one generic element",
"items":{
"type": "object",
"$ref": "#/definitions/genericComponent"
},
"minItems": 1,
"maxItems": 1
}
}}
]
}
}
}
I also tried to apply the property conditionally as:
{
"type": "object",
"required": ["results"],
"properties": {
"results": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"$ref": "#/definitions/genericComponent"
}
}
},
"definitions": {
"genericComponent": {
"type": "object",
"$id": "#/definitions/genericComponent",
"required":[
"class",
"children"
],
"properties":{
"class":{
"type": "string"
},
"children":{
"type": "array",
"items": {
"type": "object",
"anyOf":[
{"$ref":"#/definitions/genericComponent"},
{"$ref":"#/definitions/button"}
]
}
}
},
"if": {
"properties": {"class": {"pattern": "^button-rect$"}}
},
"then": {
"properties": {
"children":{
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/genericComponent"
},
"minItems": 1,
}
}
},
"else": {
"properties": {
"children":{
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/genericComponent"
}
}
}
}
}
}
}
EDIT: this one sample that pass but it shouldn't as the number of children of one of the buttons is highier of 1.
{"results": [
{
"class": "box",
"children": [
{
"class": "icon",
"children": []
},
{
"class": "button-rect",
"children": [
{
"class": "label",
"children": []
}
]
},
{
"class": "button-round",
"children": [
{
"class": "label",
"children": []
},
{
"class": "label",
"children": []
}
]
}
]
}
]
}
Your approach of applying conditionally was close, but had a few errors.
The reference for #/definitions/button couldn't be resolved, so you might have seen an error.
The regex you used in your if condition was for button-rect while you asked for it to cover button-round in your example. You could of course change it to a non anchored ^button- which sounds like what you want.
Here's the schema.
{
"type": "object",
"required": [
"results"
],
"properties": {
"results": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"$ref": "#/definitions/genericComponent"
}
}
},
"definitions": {
"genericComponent": {
"type": "object",
"$id": "#/definitions/genericComponent",
"required": [
"class",
"children"
],
"properties": {
"class": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"type": "object",
"anyOf": [
{
"$ref": "#/definitions/genericComponent"
}
]
}
}
},
"allOf": [
{
"if": {
"properties": {
"class": {
"pattern": "^button-round$"
}
}
},
"then": {
"properties": {
"children": {
"maxItems": 1
}
}
}
}
]
}
}
}
The then schema is applied to the instance. You don't need to repeat the constraints already expressed by the other part of that schema (with the $id of #/definitions/genericComponent). And by extension, you don't need the else either. (This isn't the same as writing code with an execution flow.)
You can see this schema in action against your JSON instance data at https://jsonschema.dev (link is preloaded with your schema and data)
Let me know if you have any questions. There are a number of issues with the first approach, but given this is the better approach, I'm not sure "fixing" the first one is in scope of this question. =]

JsonSchema: Validate type based on value of another property

I am using the following schema to validate my json:
{
"$schema": "http://json-schema.org/schema#",
"title": " Rules",
"description": "Describes a set of rules",
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"precedence": {
"type": "number",
"minimum": 0
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": [ "Name", "Size" ]
},
"relation": {
"type": "string",
"enum": [ "is", "is not", "is not one of", "is one of" ]
},
"value": {
"type": ["array", "string", "number"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
}
}
},
"required": ["precedence", "conditions"],
"additionalProperties": false
}
}
},
"required": ["rules"],
"additionalProperties": false
}
I want to set up a dependency to validate that when the value of the relation property has the value is one of or the value is not one of, then the type of the value property can only be array
For example, the following json should not validate because it uses the relation value is not one of and the value property is not an array:
{
"rules": [{
"precedence": 0,
"conditions": [{
"field": "Name",
"relation": "is not one of",
"value": "Mary"
}
]
}
]
}
Is it possible to set up dependencies to validate this way?
The best way to solve these kinds of problems is to separate the complex validation from the rest of the schema using definitions and include it with an allOf. In this solution, I use implication to enforce the validation.
{
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": { "$ref": "#/definitions/rule" }
}
},
"required": ["rules"],
"definitions": {
"rule": {
"type": "object",
"properties": {
"precedence": { "type": "number", "minimum": 0 },
"conditions": {
"type": "array",
"items": { "$ref": "#/definitions/condition" }
}
},
"required": ["precedence", "conditions"]
},
"condition": {
"type": "object",
"properties": {
"field": { "enum": ["Name", "Size"] },
"relation": { "enum": ["is", "is not", "is not one of", "is one of"] },
"value": { "type": ["array", "string", "number"] }
},
"required": ["field", "relation", "value"],
"allOf": [{ "$ref": "#/definitions/array-condition-implies-value-is-array" }]
},
"array-condition-implies-value-is-array": {
"anyOf": [
{ "not": { "$ref": "#/definitions/is-array-condition" } },
{ "$ref": "#/definitions/value-is-array" }
]
}
"is-array-condition": {
"properties": {
"relation": { "enum": ["is not one of", "is one of"] }
},
"required": ["relation"]
},
"value-is-array": {
"properties": {
"value": { "type": "array" }
}
}
}
}
If you are able to use the latest draft-7 version of JSON Schema, you can use if then else, as per https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-00#section-6.6
Although, using oneOf is also a valid approach, it might not be as clear to someone else inspecting your schema at a later date.
I've copied an example from an answer to another question:
If the "foo" property equals "bar", Then the "bar" property is
required
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"if": {
"properties": {
"foo": { "enum": ["bar"] }
}
},
"then": { "required": ["bar"] }
}
(You may want to check the draft support of the library you are using.)
There may be a more concise way to do this, but this will work:
{
"$schema": "http://json-schema.org/schema#",
"title": "Rules",
"description": "Describes a set of rules",
"definitions": {
"field": {
"type": "string",
"enum": ["Name", "Size"]
}
},
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"precedence": {
"type": "number",
"minimum": 0
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"oneOf": [
{
"properties": {
"field": {
"$ref": "#/definitions/field"
},
"relation": {
"type": "string",
"enum": ["is", "is not"]
},
"value": {
"type": ["string", "number"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
},
{
"properties": {
"field": {
"$ref": "#/definitions/field"
},
"relation": {
"type": "string",
"enum": ["is not one of", "is one of"]
},
"value": {
"type": ["array"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
}
]
}
}
},
"required": ["precedence", "conditions"],
"additionalProperties": false
}
}
},
"required": ["rules"],
"additionalProperties": false
}

Properties based on enum value in JSON Schema

I'm building a json schema definition which has a fixed set of controls that I've currently limited with an enum. However, not all properties are relevant for all controls.
I only want to require an options property if the controlType = dropdown
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"controlType": {
"type": "string",
"enum": ["title", "dropdown", "button"]
},
"options:": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
How can I conditionally include / require a field in a json schema?
Use IF..Then..Else new in Draft-07
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"controlType": {
"type": "string",
"enum": ["title", "dropdown", "button"]
},
"options:": {
"type": "array",
"items": {"type": "string"}
}
},
"if": {
"properties": {
"controlType": {"const": "dropdown"}
}
},
"then": {
"required": ["options"]
}
}
}
Use oneOf or anyOf
This can be useful if you have a property that has a limited number of acceptable values (such as an enum), but each possible value needs to be individually mapped.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"controlType": {
"type": "string",
"enum": ["title", "dropdown", "button"]
},
"options:": {
"type": "array",
"items": {"type": "string"}
}
},
"anyOf": [
{
"properties": {
"controlType": {"const": "dropdown"}
},
"required": ["controlType", "options"]
},
{
"properties": {
"controlType": {"const": "title"}
},
"required": ["controlType"]
},
{
"properties": {
"controlType": {"const": "button"}
},
"required": ["controlType"]
}
]
}
}
Further Reading
Conditionally require attribute
Applying subschemas conditionally
Combining schemas

Conditional validation in JSON schema on nested field

I tried searching, but I'm not quite sure how to put this in words! The point of confusion is how "required" works in JSON schema v4.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"prop1": {
"type": "array",
"items": {
"type": "object",
"properties": {
"A": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
enum:["abc","bcd"]
}
},
"required": [
"name"
]
}
}
},
"required": [
"A"
]
}
},
"prop2": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string"
}
},
"required": [
"field"
]
}
}
},
"required": [
"prop1"
]
}
Here I want to set a rule that if prop1.name=="abc" then only prop2 is required otherwise prop2 is optional, how to do that ?