JSON Schema: Using anyOf, oneOf, allOf within additionalProperties - json

I am trying to validate an object that can have arbitrary keys whose values are either an object that looks like:
{ "href": "some string" }
OR an array containing object's matching the above.
Here is what I currently have and DOES NOT WORK:
{
"$schema": "http://json-schema.org/schema#",
"id": "https://turnstyle.io/schema/links.json",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
"required": "href"
},
{
"type": "array",
"items": {
"type": "object",
"required": "href"
}
}
]
}
}
Passing example:
{
"customer": { "href": "/customer/1" },
"products": [
{ "href": "/product/1" },
{ "href": "/product/2" }
]
}
Failing example:
{
"customer": { "wrongKey": "/customer/1" },
"products": "aString"
}
Is it possible, and if so what is the proper syntax?
My assumption is that this will not work because the passing schema(s) in oneOf|anyOf|allOf of additionalProperties must apply to ALL keys falling under additionalProperties.

"required" should be an array of the properties which are mandatory in v4.
Or "required": true (or false) as part of the property in v3.
Try this:
{
"$schema": "http://json-schema.org/schema#",
"id": "https://turnstyle.io/schema/links.json",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
"properties": {
"href": {"type": "string"}
},
"required": ["href"]
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"href": {"type": "string"}
},
"required": ["href"]
}
}
]
}
}

Related

JSON Schema for child objects with different set of keys

I have JSON data of which is an array of data like
[
{
"type": "background_color",
"data": {
"backgroundColor": "F9192D"
}
},
{
"type": "banner_images",
"data": {
"images": [
{
"url": "https://example.com/abc.jpg",
"id": 3085
},
{
"url": "https://example.com/zyx.jpg",
"id": 3086
}
]
}
},
{
"type": "description_box",
"data": {
"text": "Hello 56787"
}
}
]
The data is an array of object which has two keys type and data. The type and keys of the data will be defined by the type of data it has.
Like for background_color type, the data should have backgroundColor property, while for banner_images, data should have images which is an array of other properties.
Till now, What I have done is
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"examples": [],
"required": [],
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": ["background_color", "banner_images", "description_box"]
},
"data": {
"type": "object" // How to define data property here for each use case
}
}
}
}
I'm not getting how to define the data property for each use case?
You can use if/then/else blocks to define conditional constraints.
The values of if and then are schemas. If the if schema is valid, then the then schema is applied, otherwise, the allOf subschema (allOf[0] in this example) would pass validation.
There are a few different ways to do this, but this is clean when you don't have any additional or special requirements. Please come back if you do =]
In this example, I've added banner_images...
You can test it working here.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": [
"background_color",
"banner_images",
"description_box"
]
},
"data": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "banner_images"
}
}
},
"then": {
"properties": {
"data": {
"required": [
"images"
],
"properties": {
"images": {
"type": "array"
}
}
}
}
}
}
]
}
}
For reference, here's the part of the JSON Schema draft-7 spec document that details the behaviour: https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.6

Json Schema different input formats

I'm creating some models in AWS API Gateway. I'm having problems with one that I wish it receives 2 input formats: one of the formats is just a dictionary the other is an array of dictionaries:
{
"id":"",
"name":""
}
and
[
{
"id":"",
"Family":""
},
{
"id":"",
"Family":""
},
...
{
"id":"",
"Family":""
}
]
Until now I've created the model to accept only the dictionary way:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Update",
"type": "object",
"properties": {
"id": { "type": "string"},
"name": { "type": "string"}
},
"required": ["id"]
}
Can you give me some tips to create the array of dictionaries, please. I've done some research and I found nothing but I'm following the way of the keywords oneOf and anyOf but I'm not sure.
You're on the right track with anyOf. What you should do depends on the similarity between the object (dictionary) that's by itself and the object that's in the array. They look different in your example, so I'll answer in kind, then show how to simplify things if they are in fact the same.
To use anyOf, you want to capture the keywords that define your dictionary
{
"type": "object",
"properties": {
"id": { "type": "string"},
"name": { "type": "string"}
},
"required": ["id"]
}
and wrap that inside an anyOf right at the root level of the schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Update",
"anyOf": [
{
"type": "object",
"properties": {
"id": { "type": "string"},
"name": { "type": "string"}
},
"required": ["id"]
}
]
}
To write a schema for an array of the same kind object, you need the items keyword.
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string"},
"Family": { "type": "string"}
},
"required": ["id"]
}
}
Add this in as a second element in the anyOf array, and you're golden.
If your lone object can have the same schema as your array-element object, then you can write that schema once as a definition and reference it in both places.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Update",
"definitions": {
"myObject": {
"type": "object",
"properties": {
"id": { "type": "string"},
"name": { "type": "string"}
},
"required": ["id"]
}
},
"anyOf": [
{ "$ref": "#/definitions/myObject" },
{
"type": "array",
"items": { "$ref": "#/definitions/myObject" }
}
]
}

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

why doesn't json schema validate definitions defined in required attribute

I'm trying to create a json schema that validates an object depending on its type. It picks the right definition, however, it doesn't validate the required attributes in the selected definition. Here is the json schema i am trying:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"literal": {
"type": "object",
"properties": {
"raw": { "type": "string" }
},
"required": ["raw"],
"additionalProperties": false
},
"identifier": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"],
"additionalProperties": false
}
},
"type": "object",
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"enum": ["Literal"]
},
"content": { "$ref": "#/definitions/literal" }
}
},
{
"type": "object",
"properties": {
"type": {
"enum": ["Identifier"]
},
"content": { "$ref": "#/definitions/identifier" }
}
}
],
"required": ["type"]
};
the following schema is valid, even tho its missing the "raw" property:
{ "type" : "Literal" }
thanks
There is no keyword content in JSON Schema spec.
Once you have asserted "type":"object" in root schema, there is no need to do it again in subschema.
In order to combine object type enumerated value with associated extended definition, you need allOf keyword.
Also in definitions if you use "additionalProperties": false you have to list all properties of the object (see "type": {}). For previously defined/validated properties you can just use permissive schema: {} or true.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"literal": {
"properties": {
"type": {},
"raw": { "type": "string" }
},
"required": ["raw"],
"additionalProperties": false
},
"identifier": {
"properties": {
"type": {},
"name": { "type": "string" }
},
"required": ["name"],
"additionalProperties": false
}
},
"type": "object",
"oneOf": [
{
"allOf": [
{
"properties": {
"type": {
"enum": ["Literal"]
}
}
},
{"$ref": "#/definitions/literal"}
]
},
{
"allOf": [
{
"properties": {
"type": {
"enum": ["Identifier"]
}
}
},
{"$ref": "#/definitions/identifier" }
]
}
],
"required": ["type"]
}