JSON Schema: Can I use "if" deeper than root of schema? - jsonschema

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.

Related

Can JSON Schema if statements handle nested $refs?

I have a JSON Schema using draft 2020-12 and I am trying to use an if-else subschema to check that a particular property exists based on the value of another property. Below is the if statement I am currently using. There are more properties but I have have omitted them for the sake of brevity. They are identical except the type of the property in the then statement is different. They are all wrapped in an allOf array:
{
"AValue": {
"allOf": [
{
"if": {
"$ref": "#/$defs/ValueItem/properties/dt",
"const": "type1"
},
"then": {
"properties": {
"string": { "type": "string" }
},
"required": ["string"]
}
}
]
}
}
The #/$defs/ValueItem/properties/dt being referred to is here:
{
"ValueItem": {
"properties": {
"value": {
"$ref": "#/$defs/AValue"
},
"dt": {
"$ref": "#/$defs/DT"
}
},
"additionalProperties": false
}
}
#/$defs/DT is here:
{
"DT": {
"type": "string",
"enum": [
"type1",
"type2",
"type3",
"type4"
]
}
}
I expected that when dt is encountered in a JSON instance document, the validator will check if the value of dt is type1 and then validate that an additional property called string is also present and is of type string. However, what actually happens is the validator complains that "Property 'string' has not been defined and the schema does not allow additional properties".
I assume that this is because the condition in the if statement evaluates to false so the subschema is never applied. However, I am unsure why that would be as I followed the example here when creating the if-then-else block. The only thing I can think of that is different is the use of $ref which I have in my schema but it is not in the example.
I found this answer which makes me think that it is possible to use $ref in an if statement but is it possible to use a ref that points to another ref or am I thinking about it incorrectly?
I have also tried removing the $ref from the if statement like below but it still doesn't work. Is it because of the $ref in the properties?
{
"AValue": {
"properties": {
"dt": {
"$ref": "#/$defs/DT"
}
},
"required": [
"dt"
],
"allOf": [
{
"if": {
"properties": {
"dt": {
"const": "type1"
}
}
},
"then": {
"properties": {
"string": {
"type": "string"
}
}
}
}
]
}
}
The problem is not cascading the $ref keywords. The const keyword at the if statement is not applied to the target of the $ref, but to the current location in the JSON input data. In this case to "AValue". To check if the property "dt" is of value "type1" at this point, you would need an if like this (simple solution with no $ref):
"if": {
"properties": {
"dt": {
"const": "type1"
}
},
"required": [ "dt" ]
}
Edit: Showing complete JSON Schema and error in JSONBuddy with $ref:

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"
]
}

Is there a way to set property value format requirements based on a condition of the property name?

I have a simple JSON schema:
{
"properties": {
"name": {
"type": "string"
}
},
"type": "object"
}
It requires that name property is a string. This schema does not restrict additional properties, e.g.
{
name: 'foo',
url: 'http://foo'/
}
The latter is a valid input.
Is there a way to set a property value format requirement based on a conditional property name match?, e.g. any property that contains url string in it must correspond to the following schema:
{
"type": "string",
"format": "url"
}
Therefore, an input:
{
name: 'foo',
location_url: 'not-a-valid-url'
}
would cause an error because location_url does not contain a valid URL?
I'd imagine, a schema for something like this would look like:
{
"properties": {
"name": {
"type": "string"
}
},
"matchProperties": {
"/url/i": {
"type": "string",
"format": "url"
}
}
"type": "object"
}
where matchProperties is a keyword I made up.

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.