json-schema, array of object, unique key-value - jsonschema

I have the following schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"isCorrect": {
"type": "boolean"
}
},
"required": ["value", "isCorrect"],
"additionalProperties": false
},
"minItems": 1,
"maxItems": 50,
"uniqueItems": true,
"additionalItems": false,
}
}
}
Which validates data
{
"choices": [
{
"value": "John",
"isCorrect": true
},
{
"value": "Doe",
"isCorrect": true
}
]
}
But I want isCorrect to have a single true (others false) in the array of the objects.
Is it possible to validate the uniqueness on a single key?

You can't check uniqueness based on a key, but you can use a combination of contains and maxContains to have the constraint you described.
In the properties.choices object, add the following...
"contains": {
"properties": {
"isCorrect": {
"const": true
}
}
},
"maxContains": 1,
You can check this using https://json-schema.hyperjump.io
The contains keyword makes sure that the array contains at least one item that is valid according to the subschema value.
The maxContains keyword, if the contains keyword is used, makes sure no more that number of items are valid according to the contains subschema value.
https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-6.4.4

Related

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 to use anyOf on different properties type?

In the schema below, I need items_list, price and variance as required keys. Condition is price and variance may or may not be null but both cannot be null.
Though I'm able to achieve it, I'm looking forward to if there's any shorter way to do this. Also, I'm not sure where exactly to put required and additionalProperties keys.
Any help is greatly appreciated.
{
"type": "object",
"properties": {
"items_list": {
"type": "array",
"items": {
"type": "string"
}
},
},
"anyOf": [
{
"properties": {
"price": {
"type": "number",
"minimum": 0,
},
"variance": {
"type": [
"number",
"null"
],
"minimum": 0,
},
},
},
{
"properties": {
"price": {
"type": [
"number",
"null"
],
"minimum": 0,
},
"variance": {
"type": "number",
"minimum": 0,
},
},
},
],
# "required": [
# "items_list",
# "price",
# "variance",
# ],
# "additionalProperties": False,
}
To answer the question, "can it be shorter?", the answer is, yes. The general rule of thumb is to never define anything in the boolean logic keywords. Use the boolean logic keywords only to add compound constraints. I use the term "compound constraint" to mean a constraint that is based on more that one value in a schema. In this case, the compound constraint is that price and variance can't both be null.
{
"type": "object",
"properties": {
"items_list": {
"type": "array",
"items": { "type": "string" }
},
"price": { "type": ["number", "null"], "minimum": 0 },
"variance": { "type": ["number", "null" ], "minimum": 0 }
},
"required": ["items_list", "price", "variance"],
"additionalProperties": false,
"allOf": [{ "$ref": "#/definitions/both-price-and-variance-cannot-be-null" }],
"definitions": {
"both-price-and-variance-cannot-be-null": {
"not": {
"properties": {
"price": { "type": "null" },
"variance": { "type": "null" }
},
"required": ["price", "variance"]
}
}
}
}
Not only do you not have to jump through hoops to get additionalProperties working properly, it's also easier to read. It even matches your description of the problem, "price and variance may or may not be null" (properties) but "both cannot be null" (not (compound constraint)). You could make this even shorter by inlining the definition, but I included it to show how expressive this technique can be while still being shorter than the original schema.
Looks like you have this mostly right. That's the right place to put required.
Using additionalProperties: false, you need to also define properties at the top level, additionalProperties cannot "see through" *Of keywords (applicators).
You can add properties: [prop] : true, but define all the properties.
You need to do this because additionalProperties only knows about properties within the same schema object at the same level.

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.

Json schema variable properties

Based some condition my IPV4 can have either 2 or 3 properties but those are required. How to define it. I tried below schema. I get error saying "JSON is valid against more than one schema from 'oneOf'. Valid schema indexes: 0, 1"
"IPv4Type": {
"type": "object",
"oneOf": [
{
"properties": {
"provider-address": {
"type": "string",
"format": "ipv4"
},
"customer-address": {
"type": "string",
"format": "ipv4"
},
"mask": {
"type": "number"
}
},
"required": [
"provider-address",
"customer-address",
"mask"
]
},
{
"properties": {
"provider-address": {
"type": "string",
"format": "ipv4"
},
"mask": {
"type": "number"
}
},
"required": [
"provider-address",
"mask"
]
}
]
}
A few ideas:
you can drop the oneOf , define your JSON in one object, with all 3 properties defined, but adding only "provider-address" and "mask" as required
defining "additionalProperties": false the the 2nd definition under oneOf
replacing oneOf with anyOf