JSON Schema array tuple validation with optional items - jsonschema

I'm writing a JSON schema capable of validating an array where each item has a different schema and the ordinal index of each item is meaningful but some items are optional.
However, using the current spec (2020-12) I can't use prefixItems with optional items.
To be clear:
all required items should exist and should be validated against index matching schema
missing optional items shouldn't invalidate the result
existing optional items should be validated against the index matching schema
Here is an example of the data I'm trying to validate:
(without optional array elements)
[
{
"name": "Document 1 required",
"url": "random.random/12313213.pdf"
},
{
"name": "Document 2 required",
"url": "random.random/12313213.pdf"
}
]
(with optional array elements)
[
{
"name": "Document 1 required",
"url": "random.random/1231322313.pdf"
},
{
"name": "Optional document 1",
"url": "random.random/1231356213.pdf"
},
{
"name": "Document 2 required",
"url": "random.random/1231893213.pdf"
},
{
"name": "Optional document 2",
"url": "random.random/1231336213.pdf"
}
]
Here is the current schema I'm using:
{
"type": "array",
"items": false,
"prefixItems": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Document 1 required"
},
"url": {
"type": "string",
"format": "uri"
}
}
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Document 2 required"
},
"url": {
"type": "string",
"format": "uri"
}
}
}
]
}
I've tried adding a oneOf in the optional items position with the correct schema and a stub {} but it doesn't seem to work as:
{
"type": "array",
"items": false,
"prefixItems": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Document 1 required"
},
"url": {
"type": "string",
"format": "uri"
}
}
},
{
"oneOf": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Optional document 1"
},
"url": {
"type": "string",
"format": "uri"
}
}
},
{}
]
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Document 2 required"
},
"url": {
"type": "string",
"format": "uri"
}
}
},
{
"oneOf": [
{
"type": "object",
"properties": {
"name": {
"type": "string",
"const": "Optional document 2"
},
"url": {
"type": "string",
"format": "uri"
}
}
},
{}
]
}
]
}
Also tried different approaches using contains and additionalItems. However they either don't work for multiple schemas or don't guarantee the order for the optional items.
Note: the example uses similar schemas that could be simplified but it is used to showcase the issue in question.
EDIT:
As pointed out by #Relequestual the issue is that I'm trying to mix tuple validation with list validation where the data has an arbitrary length (required + optional) with a specific schema for each item.
This is not possible to achieve with the current version of the JSON Schema specification.

Your schema isn't working out because the schema {} is always true -- therefore when you say "oneOf": [ { .. some schema .. }, {} ] you are essentially negating the first schema - because the second schema must always be true, the first schema must be false. Which is the opposite of what you want!
I think you're expecting prefixItems to be more complicated than it actually is. Each schema in the prefixItems list is already optional, in the sense that if the corresponding item is not there in the data instance, there is no failure.
For example, consider validating this data [1] against this schema:
{
"prefixItems": [
{ "type": "integer" },
false
]
}
The overall result of this evaluation is true -- the first data element validates against "type": integer, and the second schema, false, never runs because there is no item to run against. If we passed a data instance of [ 1, 1 ] then validation would fail.
If you want to ensure that all of the data items corresponding to prefixItems subschemas are actually present, then you would need to use minItems: e.g. for the above example you would add "minItems": 2.
One major caveat is that you need to put all of your required items first. You cannot interleave optional items in between required items, as the schemas in prefixItems are always applied in order, and if one of the schemas doesn't evaluate to true, there is no "skipping" of items to the next one. The first prefixItems schema always applies to the first data instance, the second prefixItems schema always applies to the second data instance, and so on.
On the other hand, if you can get away with not specifying order at all, you can use multiple contains directives (note that minContains defaults to 1 when not explicitly provided):
"allOf": [
{ "contains": { schema for one of the required items, that can be anywhere... },
{ "contains": { schema for another required item... },
{ "minContains": 0, "contains": { schema for an optional item... },
...
]
You could also put your optional items into additionalItems with anyOf (this will work even if the number of optional items is a very large or unpredictable number):
"additionalItems": {
"anyOf": [
{ ..schema of an optional item.. },
{ "" },
...
]
}

If you can move all of your mandatory non-optional items to the front of the array, you can use prefixItems to define the required items, in order, followed by additionalItems to define a single schema for all other (optional) items, assuming the additional options are uniform.
Use minItems to make sure the number of required items are present. You can use maxItems to limit the total number of items in the array, effectivly allowing you to limit the number of optional items, if you need to do so.

Related

Put validation of two array fields in JSON Schema using oneOf

Can I put check on two fields in JSON schema ? Both field are of type array of objects. Conditions:
Either one of them can contain value at a time (i.e. other should be empty).
Both can be empty.
Any leads ?
// The schema
var schema = {
"id": "https://kitoutapi.lrsdedicated.com/v1/json_schemas/login-request#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Login request schema",
"type": "object",
"oneOf": [
{ "categories": {
"maxItems": 0
},
"positionedOffers": {
"minItems": 1
}},
{ "categories": {
"minItems": 1
},
"positionedOffers": {
"maxItems": 0
}}
],
"properties": {
"categories": {
"type": "array"
},
"positionedOffers": {
"type": "array"
}
},
"additionalProperties": false
};
// Test data 1
// This test should return a good result
var data1 = {
"positionedOffers":['hello'],
"categories":[],
}
For your requirement, I think I'd come at this from the other direction. Rather than saying
If one contains a value, the other must be empty, but both may be empty.
I'd say
At least one must be empty.
That leads you to use an anyOf with subschemas checking that each property is an empty array.
{
"id": "https://kitoutapi.lrsdedicated.com/v1/json_schemas/login-request#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Login request schema",
"type": "object",
"anyOf": [
{
"properties": {
"categories": {
"maxItems": 0
}
}
},
{
"properties": {
"positionedOffers": {
"maxItems": 0
}
}
}
],
"properties": {
"categories": {
"type": "array"
},
"positionedOffers": {
"type": "array"
}
},
"additionalProperties": false
}
Bonus Material
In your original post, you omitted the properties keywords under the oneOf. This may have been the cause of the schema's failure to validate. I've added it in the above.
Secondly, draft 4 is quite old at this point. You may be limited by the implementation you're using, but if you can, you should consider using a more recent version of JSON Schema. You can view available implementations and what versions they support on the JSON Schema implementations page.

JSON schema validation for array that can have items with different keys

I am trying to find a schema that will validate a given array having multiple items. The items can have 2 possible values for a key. But all the items should have the same value as the key.
If the 2 possible values are 'primary' and 'secondary', then all the keys should be 'primary' or all the keys should be 'secondary'. oneOf does not seem to be working in this case.
Is there a solution to this? Any help is appreciated. Thank you.
Schema:
{
type: "object",
properties: {
values: {
type: "array",
uniqueItems: true,
minItems: 1,
maxItems: 100,
items: {
anyOf: [
{ $ref: "#/definitions/primaryObj"} ,
{ $ref: "#/definitions/secondaryObj"}
]
}
},
},
definitions: {
primaryObj: {
type: "object",
required: ["id", "primary"],
properties: {
id: {
type: "string",
description: "The id",
},
primary: {
type: "string",
description: "primary value",
},
},
},
secondaryObj: {
type: "object",
required: ["id", "secondary"],
properties: {
id: {
type: "string",
description: "The id",
},
secondary: {
type: "string",
description: "secondary value",
},
},
},
},
required: ["values"],
}
Sample Input -
Input 1 - should PASS validation
{
"values": [
{
"id": "1",
"primary" : "hello"
},
{
"id": "2",
"primary" : "world"
}
]
}
Input 2 - should PASS validation
{
"values": [
{
"id": "1",
"secondary" : "hello"
},
{
"id": "2",
"secondary" : "world"
}
]
}
Input 3 - should FAIL validation
{
"values": [
{
"id": "1",
"primary" : "hello"
},
{
"id": "2",
"secondary" : "world"
}
]
}
You were pretty close here. There are two changes you need to make in order to get the validation you want. (I'm going to be assuming you're using draft-07, although this applies to newer drafts also)
First, let's take the top section.
The anyOf keyword is specified as follows:
An instance validates successfully against this keyword if it
validates successfully against at least one schema defined by this
keyword's value.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.7.2
You only want ONE of the referenced subschemas to be true!
oneOf is defined similar:
An instance validates successfully against this keyword if it
validates successfully against exactly one schema defined by this
keyword's value.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.7.3
So we change your schema to check that only ONE of the references is valid...
"items": {
"oneOf": [
{
"$ref": "#/definitions/primaryObj"
},
{
"$ref": "#/definitions/secondaryObj"
}
]
}
But this still is incorrect. Let's refresh what items does.
This keyword determines how child instances validate for arrays,
and does not directly validate the immediate instance itself.
If "items" is a schema, validation succeeds if all elements in the
array successfully validate against that schema.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.4.1
It LOOKS like we got this right, however the first paragraph in the quote above attempts to convey that items applies its subschema value to each item in the array, and not "as a whole array".
What our above subschema is doing, is checking each item in the array by itself, in isolation of the other items, that they are "primary" or "secondary" as you define.
What we WANT to do, is check that ALL items in the array are either "primary" or "secondary". To achive this, we need to move the oneOf outside items.
"oneOf": [
{
"items": {
"$ref": "#/definitions/primaryObj"
}
},
{
"items": {
"$ref": "#/definitions/secondaryObj"
}
}
]
Almost there! This almost works, but we still find mixing primary and secondary doesn't cause validation to fail.
Let's check our assumptions. We assume that validation should fail when the instance data has primary and secondary in objects in the array. We can test this by changing one of the subschemas in our oneOf to false, forcing the first subschema definition (primary) to be checked. It should check that all items in the array are primary, and any secondary should cause validation failure.
We have to remember, JSON Schema is constraints based. Anything that isn't constrained, is allowed.
If we look at the definition for primaryObj, it requires and defines the validation for id and primary, but this doesn't inherintly prevent additioanl keys in the object. To do that, we need to add "additionalProperties": false` (to both definitions).
The end result looks like this. You can check out the live demo at https://jsonschema.dev/s/3ZKBp
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"values": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"maxItems": 100,
"oneOf": [
{
"items": {
"$ref": "#/definitions/primaryObj"
}
},
{
"items": {
"$ref": "#/definitions/secondaryObj"
}
}
]
}
},
"definitions": {
"primaryObj": {
"type": "object",
"required": [
"id",
"primary"
],
"properties": {
"id": {
"type": "string",
"description": "The id"
},
"primary": {
"type": "string",
"description": "primary value"
}
},
"additionalProperties": false
},
"secondaryObj": {
"type": "object",
"required": [
"id",
"secondary"
],
"properties": {
"id": {
"type": "string",
"description": "The id"
},
"secondary": {
"type": "string",
"description": "secondary value"
}
},
"additionalProperties": false
}
},
"required": [
"values"
]
}

Json schema conditional validation configuration - Unsupported keyword(s): ["const"]]

I want to set up the conditional validation in my schema. I saw an example here on SO.
I have a similar setup, where I would like to validate if the field public is set to string "public". If it is set to "public" then I want to make fields description, attachmentUrl and tags required. If the field is not set to "public" then this fields are not required.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Update todo",
"type": "object",
"properties": {
"public": {
"type": "string"
},
"description": {
"type": "string",
"minLength": 3
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true,
"minItems": 1
},
"attachmentUrl": {
"type": "string"
}
},
"anyOf": [
{
"not": {
"properties": {
"public": { "const": "public" }
},
"required": ["public"]
}
},
{ "required": ["description", "tags", "attachmentUrl"] }
],
"additionalProperties": false
}
But, when I try to deploy it like that, I get the following error:
Invalid model specified: Validation Result: warnings : [], errors :
[Invalid model schema specified. Unsupported keyword(s): ["const"]]
The "const" keyword wasn't added until draft 06. You should upgrade to an implementation that supports at least that version.
https://json-schema.org/draft-06/json-schema-release-notes.html#additions-and-backwards-compatible-changes
Otherwise, you can use "enum" with a single value: "enum": ["public"]

How do I indicate which "oneOf" API response will use?

I have an API where the basic response of one key will have an array of identifiers. A user may pass an extra parameter so the array will turn to an array of objects from an array of strings (for actual details rather than having to make a separate call).
"children": {
"type": "array",
"items": {
"oneOf": [{
"type": "string",
"description": "Identifier of child"
}, {
"type": "object",
"description": "Contains details about the child"
}]
}
},
Is there a way to indicate that the first type comes by a default and the second via a requested param?
It's not entirely clear to me what you are trying to accomplish with the distinction. Really that sounds like documentation; maybe elaborate in the descriptions of each oneOf subschema.
You could add an additional boolean field at the top level (sibling of children) to indicate whether detailed responses are returned and provide a default value for that field. The next step is to couple the value of the boolean to the type of the array items, which I've done using oneOf.
I'm suggesting something along the lines of:
{
"children": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string",
"description": "Identifier of child",
"pattern": "^([A-Z0-9]-?){4}$"
},
{
"type": "object",
"description": "Contains details about the child",
"properties": {
"age": {
"type": "number"
}
}
}
]
}
},
"detailed": {
"type": "boolean",
"description": "If true, children array contains extra details.",
"default": false
},
"oneOf": [
{
"detailed": {
"enum": [
true
]
},
"children": {
"type": "array",
"items": {
"type": "object"
}
}
},
{
"detailed": {
"enum": [
false
]
},
"children": {
"type": "array",
"items": {
"type": "string"
}
}
}
]
}
The second oneOf places a further requirement on the response object that when "detailed": true the type of items of the "children" array must be "object". This refines the first oneOf restriction that describes the schema of objects in the "children" array.

Is it possible to inline JSON schemas into a JSON document? [duplicate]

For example a schema for a file system, directory contains a list of files. The schema consists of the specification of file, next a sub type "image" and another one "text".
At the bottom there is the main directory schema. Directory has a property content which is an array of items that should be sub types of file.
Basically what I am looking for is a way to tell the validator to look up the value of a "$ref" from a property in the json object being validated.
Example json:
{
"name":"A directory",
"content":[
{
"fileType":"http://x.y.z/fs-schema.json#definitions/image",
"name":"an-image.png",
"width":1024,
"height":800
}
{
"fileType":"http://x.y.z/fs-schema.json#definitions/text",
"name":"readme.txt",
"lineCount":101
}
{
"fileType":"http://x.y.z/extended-fs-schema-video.json",
"name":"demo.mp4",
"hd":true
}
]
}
The "pseudo" Schema note that "image" and "text" definitions are included in the same schema but they might be defined elsewhere
{
"id": "http://x.y.z/fs-schema.json",
"definitions": {
"file": {
"type": "object",
"properties": {
"name": { "type": "string" },
"fileType": {
"type": "string",
"format": "uri"
}
}
},
"image": {
"allOf": [
{ "$ref": "#definitions/file" },
{
"properties": {
"width": { "type": "integer" },
"height": { "type": "integer"}
}
}
]
},
"text": {
"allOf": [
{ "$ref": "#definitions/file" },
{ "properties": { "lineCount": { "type": "integer"}}}
]
}
},
"type": "object",
"properties": {
"name": { "type": "string"},
"content": {
"type": "array",
"items": {
"allOf": [
{ "$ref": "#definitions/file" },
{ *"$refFromProperty"*: "fileType" } // the magic thing
]
}
}
}
}
The validation parts of JSON Schema alone cannot do this - it represents a fixed structure. What you want requires resolving/referencing schemas at validation-time.
However, you can express this using JSON Hyper-Schema, and a rel="describedby" link:
{
"title": "Directory entry",
"type": "object",
"properties": {
"fileType": {"type": "string", "format": "uri"}
},
"links": [{
"rel": "describedby",
"href": "{+fileType}"
}]
}
So here, it takes the value from "fileType" and uses it to calculate a link with relation "describedby" - which means "the schema at this location also describes the current data".
The problem is that most validators do not take any notice of any links (including "describedby" ones). You need to find a "hyper-validator" that does.
UPDATE: the tv4 library has added this as a feature
I think cloudfeet answer is a valid solution. You could also use the same approach described here.
You would have a file object type which could be "anyOf" all the subtypes you want to define. You would use an enum in order to be able to reference and validate against each of the subtypes.
If the sub-types schemas are in the same Json-Schema file you don't need to reference the uri explicitly with the "$ref". A correct draft4 validator will find the enum value and will try to validate against that "subschema" in the Json-Schema tree.
In draft5 (in progress) a "switch" statement has been proposed, which will allow to express alternatives in a more explicit way.