AJV - Help understand strictmode warnings - jsonschema

I have the following json schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Form Field configuration",
"properties": {
"title": {
"type": "string",
"title": "The field label"
},
"description": {
"type": "string",
"title": "The field description"
},
"presentation": {
"additionalProperties": false,
"type": "object",
"title": "Configuration for the field visual UI",
"properties": {
"inputType": {
"title": "The recommend way to represent the field visually.",
"anyOf": [
{
"const": "text",
"title": "Similar to the HTML `<input>` with text type."
},
{
"const": "number",
"title": "Similar to the HTML `<input>` with number type."
},
{
"const": "money",
"title": "Similar to 'number', dedicated to monetary values."
}
]
},
"currency": {
"title": "For inputType=money. Monetary Currency Code (3 digits)",
"maxLength": 3,
"minLength": 3,
"type": "string"
},
"description": {
"type": "string",
"title": "The field description with HTML"
}
}
},
"$template": {
"type": "string",
"title": "Pass the template URL to a given field"
}
},
"required": ["presentation"],
"allOf": [
{
"$comment": "Extend the base Draft-07 configuration",
"$ref": "http://json-schema.org/draft-07/schema#"
},
{
"$comment": "Add presentation validations only when $template is not present.",
"if": { "required": ["$template"] },
"else": {
"properties": {
"presentation": {
"$ref": "#/$defs/presentation_validations"
}
}
}
},
{
"if": {
"$comment": "If it contains presentation.description, then [root].description is also required.",
"properties": {
"presentation": {
"required": ["description"]
}
}
},
"then": {
"required": ["description"]
}
}
],
"$defs": {
"presentation_validations": {
"allOf": [
{
"required": ["inputType"]
},
{
"if": {
"properties": {
"inputType": { "const": "money" }
},
"required": ["inputType"]
},
"then": {
"required": ["currency"]
},
"else": {
"properties": { "currency": false }
}
}
]
}
}
}
If I log the strict mode warnings, I get the following warnings:
strict mode: required property "$template" is not defined at "#/allOf/1/if" (strictRequired)
strict mode: missing type "object" for keyword "required" at "#/$defs/presentation_validations/allOf/0" (strictTypes)
strict mode: required property "inputType" is not defined at "#/$defs/presentation_validations/allOf/0" (strictRequired)
strict mode: missing type "object" for keyword "required" at "#/$defs/presentation_validations/allOf/1/if" (strictTypes)
strict mode: missing type "object" for keyword "properties" at "#/$defs/presentation_validations/allOf/1/if" (strictTypes)
strict mode: missing type "object" for keyword "required" at "#/$defs/presentation_validations/allOf/1/then" (strictTypes)
strict mode: required property "currency" is not defined at "#/$defs/presentation_validations/allOf/1/then" (strictRequired)
strict mode: missing type "object" for keyword "properties" at "#/$defs/presentation_validations/allOf/1/else" (strictTypes)
strict mode: missing type "object" for keyword "required" at "#/allOf/2/if/properties/presentation" (strictTypes)
strict mode: required property "description" is not defined at "#/allOf/2/if/properties/presentation" (strictRequired)
strict mode: required property "description" is not defined at "#/allOf/2/then" (strictRequired)
But I don't understand what do I need to change and, most importantly, when I need to do them, as if I validate against some jsons, they work as expected.
Here's the codesandbox: https://codesandbox.io/s/ajv-strict-mode-warnings-b88re5

AJV's "strict mode" prevents you from making common mistakes which could lead to creating a JSON Schema which is less specified (or "more underspecified") than you expect.
Let's take the first error you see.
strict mode: missing type "object" for keyword "required" at "#/allOf/1/if/properties/presentation" (strictTypes)
The required keyword only applies to objects. This means if the instance data at that location was of another type, say, a string, then validation would pass. The required keyword doesn't mean it has to be an object. If you want it to be an object, you need to additionally specify such using the type keyword.
AJV's strict mode rule "strictTypes" explicitly requires that you specify the types if you use keywords which are only applied based on their type.
Consider a JSON Schema such as { "required": "name" }. If the instance was an object, "name" would be required. However, if the instance data was in stead an array [], it would also pass validation for the given schema in this example.

In addition to the answer of #relequestual. There is a built-in linter/analyzer in JSONBuddy and it shows a major issue in your schema immediately:
There is a $ref at the same schema as other keywords. Prior to draft 2019-09 all of the keywords next to $ref at the same level are ignored during validation.
AJV does not complain as you are using draft 7? Is this also present in your copy?
Often, a possible solution for an error like this is to use allOf and wrap the $ref and all other definitions in two separate (sub)schemas. Like:
"allOf": [
{ "$ref": "myref" },
{ ... all other definitions ... }
]

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

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

Allow additional properties in reference schemas, but none else

Suppose I have two schema being used to validate a json file.
testSchema.json
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": { "type": "string" },
"sample": { "type": "number" }
},
"anyOf": [
{ "$ref": "./testSchema2.json" },
{}
]
}
testSchema2.json
{
"$schema": "http://json-schema.org/draft-04/schema",
"type": "object",
"properties": {
"test": { "type": "string" },
"test2": { "type": "number" }
}
}
test.json
{
"$schema": "../testSchema.json",
"sample": 0,
"test": "some text" //this line throws error "Property is not allowed"
}
I'd like for the file to be validated against the included schema's properties and any schema that is referenced's properties. Am I missing something?
Edit: I want to exclude any objects that are not explicitly defined in any of my included/referenced schema.
From JSON Schema draft 2019-09 (after draft-07), this is possible by using the unevaluatedProperties keyword.
additionalProperties cannot "see through" applicator keywords such as "anyOf" and "$ref", and only works based on the properties in the same schema object.
This is not possible with draft-07 or previous.

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.