JSON Schema oneOf for array options - json

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"]}
]
}
}
}

Related

JSON Schema Grammar Not Validating Enums

I have defined the following JSON Schema Grammar which has to take "TypeA" type of elements and none other than that.
{
"$schema": "http://json-schema.org/draft-04/schema",
"title": "Schema definition",
"type": "object",
"scname": "string",
"properties": {
"itemInfo": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/definitions/TypeADefinition"
}
]
}
}
},
"required": [
"itemInfo"
],
"definitions": {
"TypeADefinition": {
"type": "object",
"properties": {
"elementOf": {
"types": {
"enum": [
"TypeA"
]
}
},
"elements": {
"items": {
"oneOf": [
{
"$ref": "#/definitions/TypeAElementDefinition"
}
]
},
"type": "array"
}
}
},
"TypeAElementDefinition": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 128
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
JSON Object 1:
{
"itemInfo": [
{
"elementOf": "TypeA",
"elements": [
{
"name": "John Doe"
}
]
}
]
}
JSON Object 2:
{
"itemInfo": [
{
"elementOf": "TypeB",
"elements": [
{
"name": "John Doe"
}
]
}
]
}
Both of these JSON objects are getting validated by the JSON grammar that I have defined but only the first JSON object should be validated successfully by the grammar the second JSON should not validated as it has elementOf "TypeB".
Is there anything that is missing in my Schema Grammar?
UPDATE
After updating the schema grammar to the following, I am able to invalidate 2nd JSON object.
{
"$schema": "http://json-schema.org/draft-04/schema",
"title": "Schema definition",
"type": "object",
"scname": "string",
"properties": {
"itemInfo": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/definitions/TypeADefinition"
},
{
"$ref": "#/definitions/TypeBDefinition"
}
]
}
}
},
"required": [
"itemInfo"
],
"definitions": {
"TypeADefinition": {
"type": "object",
"properties": {
"elementOf": {
"type": "string",
"enum": [
"TypeA"
]
},
"elements": {
"items": {
"oneOf": [
{
"$ref": "#/definitions/TypeAElementDefinition"
}
]
},
"type": "array"
}
}
},
"TypeAElementDefinition": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 128
}
},
"required": [
"name"
],
"additionalProperties": false
},
"TypeBDefinition": {
"type": "object",
"properties": {
"elementOf": {
"type": "string",
"enum": [
"TypeB"
]
},
"elements": {
"items": {
"oneOf": [
{
"$ref": "#/definitions/TypeBElementDefinition"
}
]
},
"type": "array"
}
}
},
"TypeBElementDefinition": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 128
}
},
"required": [
"name"
],
"additionalProperties": false
}
}
}
I have also added a definition for 'TypeB' in the grammar which should validate TypeB elements. But it is not happening.
TypeB element is referring to 'TypeADefinition', which should not happen. What is the issue with the grammar?
Error:
Message: Value "TypeB" is not defined in enum.
Schema path: #/definitions/TypeADefinition/properties/elementOf/enum

Conditional subschema inclusion in JSON Schema?

Depending on the property type I would like to include a subschema so I wrote:
"allOf": [
{
"if": {
"properties": { "type": { "const": "foobar" }}
},
"then": {
"$ref": "foobar.schema.json"
}
}, ...
]
Unfortunately, it doesn't work. However, if I do the following, I successfully get the foobar definitions:
"allOf": [
{
"$ref": "foobar.schema.json"
}, ...
]
It seems json-schema doesn't support conditional sub-schema inclusion or I don't know how to use it.
Any clues?
My payload is:
{
"type": "foobar",
"foobarProperty": 42
}
Here the same issue with a monolithic schema:
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"additionalProperties": false,
"definitions": {
"foo": {
"lines": {
"$id": "#/lines",
"type": "integer",
"default": 1,
},
}
},
"allOf": [
{
"if": {
"properties": { "type": { "const": "foo" }}
},
"then": {
"allOf" : [
{"$ref": "#/definitions/foo"}
]
}
},
],
"properties": {
"type": {
"$id": "#/properties/type",
"type": "string",
"enum": ["foo", "bar"],
"default": "foo"
},
}
}

Json schema permits override of fields in objects in array

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.

JSON is not getting validated based on schema

I must be missing something here, but the below JSON is not getting validated against the schema.
For example, the required attribute from the Java/JavaScript object is never getting enforced as per the schema. (FYI- Every language object may have other attributes or nested object)
However, if I completely remove the definition and directly put under array items each separately, then it validates.
I want to use 'definitions' and gets validated.The reason I have to put all the object in definitions and later I may have to put different language object in oneOf/allOf for other certain validation check.
Online check:
Schema and JSON
JSON
{
"languages": [
{
"lang": "Java",
"trainer": "Peter"
},
{
"lang": "JavaScript",
"enrolled": "42",
"available": "5"
}
]
}
and the Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"languages"
],
"properties": {
"languages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object"
},
"anyOf": [
{
"$ref": "#/definitions/Java"
},
{
"$ref": "#/definitions/JavaScript"
}
]
}
},
"definitions": {
"Java": {
"required": [
"trainer"
],
"properties": {
"lang": {
"enum": [
"Java"
]
},
"trainer": {
"type": "string"
}
}
},
"JavaScript": {
"required": [
"enrolled",
"available"
],
"properties": {
"lang": {
"enum": [
"JavaScript"
]
},
"enrolled": {
"type": "string"
},
"available": {
"type": "string"
}
}
}
}
}
The fixed schema and it works now
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"languages"
],
"properties": {
"languages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"anyOf": [
{
"$ref": "#/definitions/Java"
},
{
"$ref": "#/definitions/JavaScript"
}
]
}
}
},
"definitions": {
"Java": {
"required": [
"trainer"
],
"properties": {
"lang": {
"enum": [
"Java"
]
},
"trainer": {
"type": "string"
}
}
},
"JavaScript": {
"required": [
"enrolled",
"available"
],
"properties": {
"lang": {
"enum": [
"JavaScript"
]
},
"enrolled": {
"type": "string"
},
"available": {
"type": "string"
}
}
}
}
}
I think that the "array" definition is not correct. The objects that can be added in the array must be refereed in the "items", after you define the type of the items. Something like this:
"languages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"anyOf": [
{"$ref": "#/definitions/Java"},
{"$ref": "#/definitions/JavaScript"}
]
}
}
I have fixed myself
What I did: I have placed the json object blocks under items section of array.

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. =]