Put validation of two array fields in JSON Schema using oneOf - jsonschema

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.

Related

ajv-cli always says bad data is valid

Running ajv-cli as part of my automated testing scripts to make sure my mock data is up to date.
./node_modules/.bin/ajv -s ./test-data/manifest.schema.json -d ./test-data/fleet.manifest.json
./test-data/fleet.manifest.json valid
But the data isn't valid.
manifest.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ManifestHistoryItem": {
"properties": {
"id": {
"default": [
"assetCatalog",
"Roster"
],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"default": "",
"type": "string"
}
},
"required": [
"id",
"name"
],
"type": "object"
}
}
}
fleet.manifest.json:
{
"namee": "Epic Space Battles"
}
(it's missing the required "id" property, and "name" is misspelled)
Schema is generated from "typescript-json-schema": "^0.54.0" from a typescript model and evaluated via "ajv-cli": "^5.0.0".
Your schema declares definitions, but it doesn't reference them anywhere. You need to add a "$ref": "#/definitions/ManifestHistoryItem" at the root.
{
"definitions": {
"ManifestHistoryItem": { ... }
},
"$ref": "#/definitions/ManifestHistoryItem"
}
Either that or you can just get rid of the definitions wrapper altogether and just have the { ... } part from above.
Effectively what's happening is you've defined an empty schema, which applies no constraints, meaning all instances (data) pass.

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

How to enforce only one property value to true in an array (JSON Schema)

I am trying to have JSON validation based on the following input:
{
"elements":[
{
"..."
"isSelected": true
},
{
"..."
"isSelected": false
},
{
"..."
"isSelected": false
}
]
}
The input is going to be valid if and only if we have "isSelected" set to "true" (and all the rest set to "false"). Can't have "isSelected: true" more than once (and all the rest need to be "false").
Tried with the following:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"element":{
"type": "object",
"properties": {
"isSelected": {
"type": "boolean"
}
}
}
},
"properties": {
"elements": {
"type": "array",
"items": {
"$ref": "#/definitions/element"
},
"oneOf": [
{
"isSelected": true
}
]
}
},
}
unfortunately I don't think this is possible with json schema draft 7. the newest draft (2019-09) features the maxContains keyword, which would be able to validate this, but tooling for this draft is sparse so far. I don't know the tooling you're using, but if you are able to use 2019-09, the schema for 'elements' would look something like:
{
"type": "array",
"contains": {
"properties": {
"isSelected": {"const": true}
}
},
"maxContains": 1
}
oneOf isn't what you're looking for, for this - it checks that one of a set of schemas validates against the instance, not whether one of a set of instances validates against a schema.
This is not currently supported, but you may be interested in this proposal which intends to add a keyword to support key-based item uniqueness. It's not exactly the same, but I think it's related.

jsonSchema attribute conditionally required based on uri parameter

Consider an API with an endpoint in which you pass a parameter foo as part of the URL-path, and pass some parameters as json in the body of a POST request.
This uri parameter foo must have one of the values fooBar and fooBaz. Otherwise the request gest a 404.
If the value of foo is fooBar then the body attribute bar is required.
If the value of foo is fooBaz then the body attribute baz is required.
How do I specify a jsonSchema for such an endpoint?
An example for such a request would be:
POST /path/to/endpoint/fooBar HTTP/1.1
Accept: application/json
Content-Type: application/json
Content-Length: 20
{"bar":"something"}
So far I came up with the following based on this and that but I have no idea if this is correct.
{
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "http://json-schema.org/draft-03/schema#",
"definitions": {
"foo": { "enum": ["fooBar", "fooBaz"] }
},
"type": "object",
"properties": {
"foo": { "$ref": "#/definitions/foo" },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"links": [{
"rel": "self",
"href": "/path/to/endpoint/{foo}",
"hrefSchema": {
"properties": {
"foo": {"$ref": "#/definitions/foo"}
}
}
}],
"anyOf": [
{
"properties": {
"foo": { "enum": ["fooBar"] }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "enum": ["fooBaz"] }
},
"required": ["baz"]
},
]
}
JSON Schema validation is not aware of the URI that the data being validated came from (if any). You can use JSON Hyper-Schema to tell your users how to send that data in the way you expect, but you will still need to do an additional check on the server-side to validate that the request was sent properly.
Before I get into the solution, I want to point out a couple things. Your $schema is set to "draft-03". If you really need "draft-03", this solution won't work. anyOf, allOf and the array form of required were added in "draft-04". hrefSchema was added in "draft-06". Since "draft-06" is brand new and is not well supported yet and you don't need hrefSchema anyway, I'm going to assume "draft-04" ("draft-05" was effectively skipped).
The next thing to mention is that you are using id wrong. It should be the identifier for your schema. You usually don't need this, but if you do, it should be the full URI identifying your schema. That is how my solution uses it.
Last thing before I get into the solution. If you are using the link keyword, you are using JSON Hyper-Schema and your $schema should reflect that. It should have "hyper-schema" instead of "schema" at the end of the URI.
Now the solution. I broke it into two parts, the schema that you put through the validator and the hyper-schema that tells users how to make the request. You've got the first one right. I only fixed the $schema and id.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/schema/my-foo-schema",
"type": "object",
"properties": {
"foo": { "enum": ["fooBar", "fooBaz"] },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"anyOf": [
{
"properties": {
"foo": { "enum": ["fooBar"] }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "enum": ["fooBaz"] }
},
"required": ["baz"]
}
]
}
Next is the hyper-schema. You can't reference anything external (href, instance data) in a request schema, but you can write your schema so that it matches your href. The duplication is unfortunate, but that's the way you have to do it.
{
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
"links": [
{
"rel": "http://example.com/rel/my-foo-relation",
"href": "/path/to/endpoint/fooBar",
"method": "POST",
"schema": {
"allOf": [{ "$ref": "http://example.com/schema/my-foo-schema" }],
"properties": {
"foo": { "enum": ["fooBar"] }
}
}
},
{
"rel": "http://example.com/rel/my-foo-relation",
"href": "/path/to/endpoint/fooBaz",
"method": "POST",
"schema": {
"allOf": [{ "$ref": "http://example.com/schema/my-foo-schema" }],
"properties": {
"foo": { "enum": ["fooBaz"] }
}
}
},
{
"rel": "self",
"href": "/path/to/endpoint"
}
]
}
Usually when I come across something that is difficult to model with hyper-schema, it means that I am doing something overly complicated with my API and I need to refactor. I suggest taking a few minutes to think about alternative designs where this switching on an enum isn't necessary.

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.