JSON SCHEMA Adding dependant element with optional property - jsonschema

I have a JSON SCHEMA problem related to dependant elements.
Here is an basic example of elements who depends on "droplist", if you choose "Option1", both "example1" and "example2" are activated. If you choose "Option2", only "example2" is activated.
{
"type": "object",
"properties": {
"droplist": {
"title": "Choose one",
"type": "string",
"enum": [
"Option1",
"Option2"
]
},
"example1": {
"title": "Example field 1",
"type": "string",
"options": {
"dependencies": {
"droplist": "Option1"
}
}
},
"example2": {
"title": "Example field 2",
"type": "string",
"options": {
"dependencies": {
"droplist": ["Option1", "Option2"]
}
}
}
}
}
Well, I want to achieve the following:
Keep the dependant fields, but if I choose "Option1" and both elements "example1" and "example2" appear, I want "example2" with additional {"minLength": 3} property.
If I Choose "Option2", I expect the original element "example2" WITHOUT minLength property.
Is this possible? Adding an extra property to a dependant element.
Summing up, I want element "example2" to appear with or without a property (minLength in this case), like a dynamic one.
Thanks guys.

You can do this by splitting off your definitions to a place where they can be reused -- this is under definitions in versions up to draft7, and $defs thereafter. You are using the dependencies keyword, which was split into dependentSchemas and dependentRequired in draft2019-09, so I assume you are using draft7 (or earlier).
definitions:
option1:
...
option2_without_minLength:
...
option2:
allOf:
- $ref: '#/definitions/option2_without_minLength'
- minLength: 3
properties:
...
... things using $ref: '#/definitions/option1' etc

Related

JSON Schema array tuple validation with optional items

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.

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: Can I use "if" deeper than root of schema?

I want to validate objects like this
{
type: "user",
data: {id: 1},
}
and
{
type: "account",
data: {uuid: "xxxx"},
}
I thought I can write schema like this
{
type: "object",
properties: {
type: {enum: ["user", "account"]},
data: {
"if": {properties: {type: {const: "user"}}},
"then": {
type: "object",
properties: {
id: {type: "number"}
}
},
"else": {
type: "object",
properties: {
uuid: {type: "string"}
}
},
}
}
}
but it looks like I can't refer to root type field from context of data field. So I have two questions. Can I use if deeper than root? If I can then how to refer to parent or root object from nested object? I'm using node ajv for validation.
You're correct, you cannot apply validation to a different part of your data than where your subschems is being applied. Let me explain a little.
The first thing that happens when processing a JSON Schema is the Schema as a whole is "applied" to the instance as a whole.
properties is an applicator keyword, in that it doesn't assert any validation rules by itself. The VALUES of a properties object are subschemas (which are Schemas in their own right) which are APPLIED to the instance location value when the associated KEY matches.
To give you an exanple, in your Schema, the subschema at properties > type is applied to instance location type, which means the VALUE of the object key type. If type didn't exist in your data, the subschema in your Schema wouldn't do anything (it wouldn't be applied anywhere).
This is broadly the processing model of a Schema against an instance. Hopefully this explains why you can't work in the way you're expecting. However it's still possible to achive what you want.
then and else are conditional applicator keywords, and so need to be applied following the same model.
What you need to do is use if, then, else at the top level, but have deep / nested application of the validation you want to do.
Here's a demo with a new Schema and instance
https://jsonschema.dev/s/sejHF
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"type": {
"enum": [
"user",
"account"
]
},
"data": {}
},
"if": {
"properties": {
"type": {
"const": "user"
}
}
},
"then": {
"type": "object",
"properties": {
"data": {
"properties": {
"id": {
"type": "number"
}
}
}
}
},
"else": {
"type": "object",
"properties": {
"data": {
"properties": {
"uuid": {
"type": "string"
}
}
}
}
}
}
If you also want to make sure that id is present when type is user, then you also need to add required: ['id'] in the then subschema. (You'll need to do similar in the else clause if you want to check for uuid too.

How to show same field based on an or condition on alpaca by binding a field to multiple dependencies

I am using alpacajs to render an HTML form. My requirement is to show certain fields based on a checkbox. there are three checkboxes in my form and I want to show some fields if any of the checkboxes are checked.
I went through alpaca documentation on conditional dependencies and couldn't find a soution to my problem. I can solve this by creating same fields with different names but that's against dry principle.
My code looks like below.
"myCheckBox1": {
"type": "boolean"
},
"myCheckBox2": {
"type": "boolean"
},
"usernameField": {
"label": "Username *",
"order": 6,
"type": "string"
"disallowOnlyEmptySpaces": true,
"showMessages": false,
"disallowEmptySpaces": true,
}
I want to show usernameField if either of myCheckBox1 or myCheckBox2 is checked. I altered schema to show conditional dependencies.
"dependencies": {
"usernameField": [
"myCheckBox1"
],
"usernameField": [
"myCheckBox2"
]
}
But that consider only last given condition ie, it will display usernameField only if myCheckBox2 is checked and does not consider myCheckBox1. I tried below code also:
"dependencies": {
"usernameField": [
"myCheckBox1", "myCheckBox2"
]
}
But that evaluates to an and condition. Is there a way to show same field using different dependencies?
Why you don't use a group of checkboxes using enum type like the following :
// schema
"check": {
"title": "Select...",
"type": "string",
"enum": ["First", "Second"]
},
...
// options
"check": {
"type": "checkbox"
}
Then you use conditional dependencies like this:
"username": {
"dependencies": {
"check": [
'First', // if First is selected
'Second', // if Second is selected
'First,Second' // if both First and Second are selected
]
}
}
Here's a working fiddle for this.
Please tell me if this isn't what you're looking for.

reusing an object for multiple JSON schemas

I have two separate JSON schemas (used to validate HTTP request endpoints for a REST API) where they both accept the same exact object, but have different required fields (this is a create vs update request). Is there a way I can reuse a single definition of this object and only change the required fields? I know how to use $ref for reusing an object as a property of another object, but I cannot figure out how to reuse an entire object as the top-level object in a schema. My failed attempt so far:
event.json
{
"id": "event",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"start_date": {
"type": "integer"
},
"end_date": {
"type": "integer"
},
"description": {
"type": "string"
}
},
"additionalProperties": false
}
event-create.json
{
"id": "event-create",
"type": "object",
"$ref": "event",
"additionalProperties": false,
"required": [ "name", "description" ]
}
Obviously that doesn't work. It seems like it tries to insert the entirety of 'event' into the definition of 'event-create', including the ID and such. I tried referincing event#/properties to no avail. I can't seem to do a $ref as the sole value inside a properties property either. Any ideas?
Any members other than "$ref" in a JSON Reference object SHALL be ignored.
- https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03#section-3
This is why your example doesn't work. Anything other than the $ref field is supposed to be ignored.
Support for $ref is limited to fields whose type is a JSON Schema. That is why trying to use it for properties doesn't work. properties is a plain object whose values are JSON Schemas.
The best way to do this is with allOf. In this case allOf can sort-of be thought of as a list of mixin schemas.
{
"id": "event-create",
"type": "object",
"allOf": [{ "$ref": "event" }],
"required": ["name", "description"]
}
I found some syntax that seems to work, but I'm not terribly happy with it:
{
"id": "event-create",
"allOf": [
{ "$ref": "event" },
{ "required": [ "name", "description" ] }
]
}
Seems like an abuse of the allOf operator, particularly for another case where there are no required fields (thus only one element insid the allof). But it works, so I'm going with it unless someone has a better idea.