Reputation: 51
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:
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.
Upvotes: 2
Views: 1490
Reputation: 53996
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.. },
{ "" },
...
]
}
Upvotes: 3
Reputation: 12355
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.
Upvotes: 1