Why does JSON Schema maximum not work in allOf? - json

Given the following JSON schema:
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"properties": {
"pageA": {
"properties": {
"a": { "type": "number" },
},
"allOf": [
{
"if": {
"properties": {
"a": { "maximum": 10 }
}
},
"then": {
"properties": {
"b": { "type": "number" },
"c": { "type": "string" }
}
},
"else": {
"allOf": [
{
"if": {
"properties": {
"a": { "maximum": 20 }
}
},
"then": {
"properties": {
"e": { "type": "number" },
"f": { "type": "string" }
}
},
"else": {
"allOf": [
{
"if": {
"properties": {
"a": { "maxiumum": 30 }
}
},
"then": {
"properties": {
"i": { "type": "number" },
"j": { "type": "string" }
}
},
"else": {
"properties": {
"k": { "type": "number" },
"l": { "type": "string" }
}
}
}
]
}
}
]
}
}
]
}
}
}
I would expect:
{
"pageA": {
"a": 31,
"k": "50"
}
}
To generate "Invalid type. Expected Number but got String." but it is valid. I understand any properties nested within an allOf cannot be caught by additionalProperties, and it appears that the validation is also applying in the similar way.

There are two problems with your schema. The first is super simple to solve. A typo (We've all done it).
"a": { "maxiumum": 30 } Should read maximum.
Now let's take a look at the specification to see what maximum does...
The value of "maximum" MUST be a number, representing an inclusive
upper limit for a numeric instance.
If the instance is a number, then this keyword validates only if the
instance is less than or exactly equal to "maximum".
https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.2.2
Given 30 is exactly equal to 30, you triggered your then clause as opposed to your else clause.
If you want the value to be EXCLUSIVE, there's another keyword... exclusiveMaximum, which does just that.
You can see that in action here: https://jsonschema.dev/s/8Yi6e

Related

Why is the json object valid against this conditional schema?

Here is the json object.
{
"payment": {
"account": [
{
"type": "ACCOUNT_INFORMATION",
"identification": "2451114"
},
{
"type": "XXX",
"identification": "2451114"
}
]
}
}
And this is the schema.
{
"if": {
"properties": {
"payment": {
"properties": {
"account": {
"items": {
"properties": {
"type": {
"const": "ACCOUNT_INFORMATION"
}
}
}
}
}
}
}
},
"then": {
"properties": {
"payment": {
"properties": {
"account": {
"items": {
"properties": {
"identification": {
"maxLength": 8,
"minLength": 8
}
}
}
}
}
}
}
}
}
If remove the second account items as follows, the schema gives error.
{
"payment": {
"account": [
{
"type": "ACCOUNT_INFORMATION",
"identification": "2451114"
}
]
}
}
Is this due to the conditional schema cannot be apply to an embedded array?
Validation used https://www.jsonschemavalidator.net/
The first json object returns no error while the second one returns error with violation of minLength constraint.
Should both return error?
To see what's happening, let's break down the schema to focus on the critical part of the if schema.
"items": {
"properties": {
"type": { "const": "ACCOUNT_INFORMATION" }
}
}
Given this schema, the following instance is not valid because not all "type" properties have the value "ACCOUNT_INFORMATION".
[
{
"type": "ACCOUNT_INFORMATION",
"identification": "2451114"
},
{
"type": "XXX",
"identification": "2451114"
}
]
And, this following value is valid because all "type" properties have the value "ACCOUNT_INFORMATION".
[
{
"type": "ACCOUNT_INFORMATION",
"identification": "2451114"
}
]
That difference in validation result is the reason these two values behave differently in your schema. The then schema is applied only when the if schema evaluates to true, which is what you get with the second example and not the first. The then schema is applied on the second example and the minLength constraint causes validation to fail.
It seems like your conditional only applies to the items schema, so you can solve this by moving your conditional into that object only.
{
"properties": {
"payment": {
"properties": {
"account": {
"items": {
"if": {
"properties": {
"type": {
"const": "ACCOUNT_INFORMATION"
}
},
"required": ["type"]
},
"then": {
"properties": {
"identification": {
"maxLength": 8,
"minLength": 8
}
}
}
}
}
}
}
}
}

Json Schema Not working for nested Attributes

I am trying to add some validation to my json schema . I am validating json schema against json using this website https://www.jsonschemavalidator.net/. I am not able to put validation on eventPayload/totalAmount based on value present in eventName. It is not failing when it should fail. Should I give the whole path of eventName attribute as it is not present in eventPayload ? If yes, how to do that.
"totalAmount": {
"type": [
"integer",
"number"
],
"minLength": 1,
"multipleOf": 0.01,
"if": {
"properties": {
"eventName": {
"enum": [
"Test10",
"Test12"
]
}
}
},
"then": {
"properties": {
"totalAmount": {
"exclusiveMinimum": 0
}
}
},
"else": {
"if": {
"properties": {
"eventName": {
"enum": [
"Test1",
"Test2",
"Test3"
]
}
}
},
"then": {
"properties": {
"totalAmount": {
"exclusiveMaximum": 0
}
}
}
}
}
It is not possible to reference values up the tree (e.g. totalAmount is below eventName), you have to define from the top down. Using oneOf (instead of if/then/else) and schema composition, you could solve it as follows (minimal example):
Schema:
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": {
"eventName": {
"type": "string",
"enum": ["Test10", "Test12", "Test1", "Test2", "Test3"]
},
"eventPayload": {
"type": "object",
"properties": {
"totalAmount": {
"type": "number"
}
}
}
},
"oneOf": [
{
"properties": {
"eventName": {
"enum": ["Test10", "Test12"]
},
"eventPayload": {
"properties": {
"totalAmount": {
"exclusiveMinimum": 0
}
}
}
}
},
{
"properties": {
"eventName": {
"enum": ["Test1", "Test2", "Test3"]
},
"eventPayload": {
"properties": {
"totalAmount": {
"exclusiveMaximum": 0
}
}
}
}
}
]
}
Here, we first define the general structure in the properties object (no validation yet, just the type of expected objects). Then, in the oneOf array we add the alternatives: If eventName is either "Test10" or "Test12" apply the exclusiveMinimum, if it is one of the others, apply exclusiveMaximum.
Any incoming json has to fulfill both the schema defined in properties and one of the schemas in oneOf. This way of layering schemas is how json schema implements composition. Using this schema and https://www.jsonschemavalidator.net/ we can verify that it
accepts
{
"eventName": "Test12",
"eventPayload": {
"totalAmount": 5
}
}
and
{
"eventName": "Test2",
"eventPayload": {
"totalAmount": -5
}
}
but rejects
{
"eventName": "Test12",
"eventPayload": {
"totalAmount": -5
}
}
and
{
"eventName": "Test2",
"eventPayload": {
"totalAmount": 5
}
}
The properties keyword in your if clause will evaluate to true if property eventName is not present. To ensure that it is, add "required": ["eventName"] to the condition.

Json Schema Polymorphism Validate with anyOf

I've got a Pet object that could be either a dog or a cat
Depending on what noise they make I'd like to then be able to validate other fields.
schema:
{
"$id": "http://example.com",
"definitions": {
"pet": {
"type": "object",
"properties": {
"noise": {
"enum": [
"bark",
"meow"
]
}
}
},
"dog": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "bark"
},
"tail": {
"enum": [
"short",
"long"
]
}
}
},
"cat": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "meow"
},
"tail": {
"enum": [
"wavy",
"slinky"
]
}
}
}
},
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/dog",
"$ref": "#/definitions/cat"
}
]
}
}
}
}
This works when running the following json through:
{"pets":[{"noise":"meow","tail":"wavy"}]}
but not when running:
{"pets":[{"noise":"bark","tail":"long"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
or
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
I can get this working by using if/else in the json schema, but requires another type to avoid a circular dependency:
"petWithConstraints": {
"$ref":"#/definitions/pet",
"allOf": [
{
"if": {
"properties": {
"noise": {
"const": "bark"
}
}
},
"then": {
"$ref": "#/definitions/dog"
}
},
{
"if": {
"properties": {
"noise": {
"const": "meow"
}
}
},
"then": {
"$ref": "#/definitions/cat"
}
}
]
}
}
This means for every new definition it also requires another if statement.
Is there a better method of doing this? (without the extra definition/if statement)
For those that come across this, this was a syntactical error.
Each ref should have been in it's own code block.
The corrected part of the schema looks like the following:
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{ // Notice each $ref is encapsulated in it's own block
"$ref": "#/definitions/cat"
},
{
"$ref": "#/definitions/dog"
}
]
}
}
}
Running the following json through gave expected results
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"},{"noise":"meow","tail":"short"}]}
[$.pets[4]: should be valid to one and only one of the schemas ]

Use conditional statements on json schema based on another schema object

I have a json object like:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
The sdk name can either be android or ios. And the session_id is based on name field in sdk json. I have written a json schema using conditional statement (Using draft 7) as follows:
But it works in an unexpected manner:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"additionalProperties": false,
"properties": {
"session": {
"$ref": "#/definitions/Session"
},
"sdk": {
"$ref": "#/definitions/SDK"
}
},
"title": "Base"
},
"Session": {
"type": "object",
"additionalProperties": false,
"properties": {
"start_timestamp": {
"type": "integer",
"minimum": 0
},
"session_id": {
"type": "string",
"if": {
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
},
"then": {
"pattern": "A"
},
"else": {
"pattern": "B"
}
}
},
"required": [
"session_id",
"start_timestamp"
],
"title": "Session"
},
"SDK": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string"
},
"name": {
"type": "string",
"enum": [
"ios",
"android"
]
}
},
"required": [
"name",
"version"
],
"title": "SDK"
}
}
}
So the following JSON Passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "ios",
"version": "21"
}
}
But this fails:
{
"session": {
"session_id": "B",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
can someone explain y?.. Even this passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
I think you're having a similar problem as in this question.
#Relequestual is right in that you need the properties keyword around your SDK callout. But for what you want to do, you need to reorganize.
Subschemas only operate on their level in the instance, not at the root.
Consider this schema for a simple JSON object instance containing a one and a two property:
{
"properties": {
"one": {
"enum": ["yes", "no", "maybe"]
},
"two": {
"if": {
"properties": {
"one": {"const": "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
...
}
}
}
}
The if keyword under the two property can only consider the portion of the instance under the two property (i.e. two's value). It's not looking at the root of the instance, so it can't see the one property at all.
To make it so that the subschema under the two property subschema can see the one property in the instance, you have to move the if outside of the properties keyword.
{
"if": {
"properties": {
"one": {"const" : "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
... // assert two here, or have another if/then/else structure to test the one property some more
}
}
For two possible values of one, this is pretty good. Even three possible values isn't bad. However, as the possible values of one increases, so does the nesting of ifs, which can make your schema horrible to read (and possibly make validation slower).
Instead of using the if/then/else construct, I suggest using an anyOf or oneOf where each subschema represents a valid state for the instance, given the varying values of one.
{
"oneOf": [
{
"properties": {
"one": {"const": "yes"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "no"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "maybe"},
"two": ... // do some assertions on the two property here
}
}
]
}
This is much cleaner in my opinion.
Hopefully that explanation helps you reconstruct your schema to allow those other instances to pass.
You have to move your conditional to a high enough level to be able to reference all of the the properties it needs to reference. In this case, that's the /definitions/Base schema. Then you just need to write your schemas properly as Relequestual explained.
{
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"properties": {
"session": { "$ref": "#/definitions/Session" },
"sdk": { "$ref": "#/definitions/SDK" }
},
"allOf": [
{
"if": {
"properties": {
"sdk": {
"properties": {
"name": { "const": "ios" }
}
}
},
"required": ["sdk"]
},
"then": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "A" }
}
}
}
},
"else": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "B" }
}
}
}
}
}
]
},
...
}
The value of if must be a JSON Schema. If you were to take lines https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) and use that as a JSON Schema by itself, you would impose no validation constraints, because there are no JSON Schema key words at the top level of the object.
{
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
}
This is allowed in the specification, because people may want to extend the functionality of JSON Schema by adding their own key words. So it's "valid" JSON Schema, but doesn't actually DO anything.
You Need to add properties to the schema for it to make sense.
{
"properties": {
"SDK": {
"properties": {
"name": {
"const": "ios"
}
}
}
}
}
Additionally, enum must be an array. When you only have a single item, you may use const.

JSON Schema: How to require to have one of the property names or none of them and any different name?

There is msgId1 that must have a1 and msgId2 that must have a2.
This should be valid:
{ "msgId1": { "a1": "b1" } }
This too:
{ "msgId2": { "a2": "b2" } }
And this:
{ "msgIdUnknownYet": { "a3": "b3" } }
That's invalid:
{
"msgId1": { "a1": "b1" },
"msgId2": { "a2": "b2" }
}
That should be invalid too:
{
"msgId1": { "abc": "b1" },
"msgId2": { "a2": "b2" }
}
If I use oneOf, then it doesn't allow the unknown messages. And it also allows to add wrong msgId1 or msgId2 if at least one of the right msgId is present. How to write a schema for that?
You can use the dependencies for this.
{
"type": "object",
"properties": {
"msgId1": { "required": ["a1"] },
"msgId2": { "required": ["a2"] }
},
"dependencies": {
"msgId1": { "not": { "required": ["msgId2"] } }
}
}
This effectively says that if "msgId1" is present, then "msgId2" can not be present. This way, it still passes if neither "msgId1" or "msgId2" are present.
Works if there is a way to isolate them and limit the total number of properties. This tells: if there is a property with such name, then it must have that specific form, and the total number of properties is no more than 1.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"header": {
"type": "object",
"properties": {
"id": { "type": "integer" }
},
"required": ["id"]
},
"messageHeader": {
"type": "object",
"properties": {
"token": { "type": "string" }
},
"required": ["token"]
},
"msgId1": {
"type": "object",
"properties": { "a1": { "type": "string" } },
"required": ["a1"]
},
"msgId2": {
"type": "object",
"properties": { "a2": { "type": "string" } },
"required": ["a2"]
}
},
"type": "object",
"dependencies": {
"msgId1": { "type": "object", "properties": { "msgId1": { "$ref": "#/definitions/msgId1" } } },
"msgId2": { "type": "object", "properties": { "msgId2": { "$ref": "#/definitions/msgId2" } } }
},
"maxProperties": 1
}