How do I use the `If` `then` `else` condition in json schema? - jsonschema

A relatively new addition to JSON Schema (draft-07) adds the if, then and else keywords. I cannot work out how to use these new key words correctly. Here is my JSON Schema so far:
{
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "string"
}
},
"if": {
"properties": {
"foo": {
"enum": [
"bar"
]
}
}
},
"then": {
"required": [
"bar"
]
}
}
If the "foo" property equals "bar", Then the "bar" property is required. This works as expected.
However, if the "foo" property does not exist or input is empty then I don't want anything. how to acheive this?
empty input {}.
Found Errors:
Required properties are missing from object: bar.
Schema path: #/then/required
I use online validation tool:
https://www.jsonschemavalidator.net/

The if keyword means that, if the result of the value schema passes validation, apply the then schema, otherwise apply the else schema.
Your schema didn't work because you needed to require "foo" in your if schema, otherwise an empty JSON instance would pass validation of the if schema, and therefore apply the then schema, which requires "bar".
Second, you want "propertyNames":false, which prevents having any keys in the schema, unlike if you were to set "else": false which would make anything always fail validation.
{
"type": "object",
"properties": {
"foo": {
"type": "string"
},
"bar": {
"type": "string"
}
},
"if": {
"properties": {
"foo": {
"enum": [
"bar"
]
}
},
"required": [
"foo"
]
},
"then": {
"required": [
"bar"
]
},
"else": false
}

Can't you just use the "else" property ?
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"if": {
"properties": {
"foo": {
"enum": ["bar"]
}
}
},
"then": {
"required": ["bar"]
},
"else": {
"required": []
}
}

If json schema in html file, you may use such php construction.
Considering that in html file json is a simple string, we may use such php language construction as echo to output any additional strings depending situations or conditions.
<script type="application/ld+json">
{
"#context": "https://schema.org/",
"#type": "Product",
"name": "Product",
"image": "image",
"description": "description",
"brand": {
"#type": "Brand",
"name": "brand"
},
"offers": {
"#type": "Offer",
"url": "url",
"priceCurrency": "currency",
"price": "5000",
"availability": "http://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition"
}<?php if ($aggregateRating) echo ',' ?>
<?php if ($aggregateRating) echo '
"aggregateRating": {
"#type": "AggregateRating",
"ratingValue": "' . $ratingValue . '",
"ratingCount": "' . $ratingCount . '",
"bestRating": "' . $bestRating . '",
"worstRating": "' . $worstRating . '"
}';
?>
}
</script>

Related

How to improve JSON Schema validation results when using "anyOf"

We want to validate an array of elements with a JSON Schema Validator (https://github.com/networknt/json-schema-validator if that makes any difference).
Unfortunately we get quite ambiguous error messages if the validation fails and I am wondering if there is a way to improve this.
To make it clearer, I have created a small sample:
Each element in the array has a property "pet_type" which we can be sure to be there all the time (yeah I know that is discussable, but for the sake of argument let's ignore that for now). Each element has then some other properties.
This is the json schema right now.
Envelope (Basically with an array of elements)
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "./Cat.json"
},
{
"$ref": "./Dog.json"
}
]
}
}
}
}
Cat (one of the elements in the array)
{
"title": "Cat",
"allOf": [
{
"$ref": "Pet.json"
},
{
"type": "object",
"properties": {
"hunts": {
"type": "boolean"
},
"age": {
"type": "integer"
},
"pet_type": {
"type": "string",
"enum": [
"Cat"
],
"pattern": "Cat"
}
},
"required": [
"pet_type",
"age"
]
}
]
}
Dog:
{
"title": "Dog",
"allOf": [
{
"$ref": "Pet.json"
},
{
"type": "object",
"properties": {
"bark": {
"type": "boolean"
},
"breed": {
"type": "string"
},
"pet_type": {
"type": "string",
"enum": [
"Dog"
],
"pattern": "Dog"
}
},
"required": [
"bark"
]
}
]
}
And the Pet.json, which specifies that "pet_type" is required on each of the elements.
{
"title": "Pet",
"type": "object",
"discriminator": {
"propertyName": "pet_type"
},
"properties": {
"pet_type": {
"type": "string",
"enum": [
"Cat",
"Dog"
]
}
},
"required": [
"pet_type"
]
}
The problem I have is that the error messages get very long, because the element is validated against all of the constraints, without narrowing it down a bit.
Let's say the JSON to validate looks like this:
{
"pets": [
{
"pet_type": "Cat",
"hunts": true
}
]
}
"pet_type" is set to "Cat", so for a human it is "clear" that it should only validate against the Cat JSON Schema and then show that "age" is missing.
What really happens is that there are 4 violations:
age is missing (from the Cat JSON Schema)
bark is missing (from the Dog JSON Schema)
pet_type is violating the "Dog" enum contstraint (from the Dog JSON Schema)
pet_type is violating the "Dog" regex pattern (from the Dog JSON Schema)
Note: I have added the enum/regex pattern in an act of trying to change the behavior, but it did not work.
I understand from a technical standpoint why it behaves like it does, I just want to know if it is possible to tell the validator somehow to first narrow down the validation based on pet_type and then keep validating?
What I tried to get it working:
I have tried to set the pet_type to string with an enum "Cat" and "Dog" and added one of those values to the Cat/Dog JSON Schema respectively in an attempt to make it clear which event has which pet_type.
I have tried to remove the Pet.json Schema completely, but that did not change anything.
I have tried to use if/then, but somehow this gets rid of all validation errors:
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"if": {
"properties": {
"pet_type": {
"const": "Cat"
}
}
},
"then": {
"$ref": "./Cat.json"
}
},
{
"if": {
"properties": {
"pet_type": {
"const": "Dog"
}
}
},
"then": {
"$ref": "./Dog.json"
}
}
]
}
}
}
}
My sample I tried to validate gives no errors anymore:
{
"pets": [
{
"pet_type": "Cat",
"hunts": true
}
]
}
Turns out the solution with if/then was correct, and the json-schema-validator actually detects the problems, but has some code that should improve the error messages, but gets rid of the validation errors instead.
I will bring the problem to their attention.
For now I have found a workaround to use nested if/then/else statements:
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"if": {
"properties": {
"pet_type": {
"const": "Cat"
}
}
},
"then": {
"$ref": "./Cat.json"
},
"else": {
"if": {
"properties": {
"pet_type": {
"const": "Dog"
}
}
},
"then": {
"$ref": "./Dog.json"
},
"else": {
"properties": {
"pet_type": {
"type": "string",
"enum": [
"Cat",
"Dog"
]
}
}
}
}
}
}
},
"additionalProperties": false
}

JSON Schema object types not working for $ref definitions

I am using JSON Schema Draft-07. I am having a JSON Schema like below that works as expected
{
"tests": {
"type": "object",
"required": [
"name",
"desc"
],
"properties": {
"name": {
"type": "string"
},
"desc": {
"type": "string"
}
}
}
}
But if I refer the same type using definition, siblings properties\rules doesn't take affect , for e.g.
JSON Schema Store
{
"my-schema.json": {
"$id": "my-schema.json",
"$schema": "http://json-schema.org/draft-07/schema",
"definitions": {
"objectParameter": {
"type": "object"
}
},
"properties": {
"tests": {
"$ref": "#/definitions/objectParameter",
"properties": {
"desc": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"name",
"desc"
]
}
},
"required": [
"tests"
],
"type": "object"
}
}
Not sure what's the difference $ref is bringing here.
Validation Data : "tests": {"namer": "12", "desc": "12"} or "tests": {} should fail but getting passed .
"doesn't work" isn't very specific, but is it possible you are using an implementation that follows draft specification version 7 or earlier, in which the presence of a $ref keyword nullifies the effect of any other sibling keywords? If so, you can resolve the situation by wrapping an "allOf" around the subschemas:
"allOf": [
{ "$ref": ... },
{
"required": ...,
"properties": { ... }
}
]

How to extent an object with 1 parameter in json-schema

I wrote one JSON schema before, but now, as I am trying to make it a bit more advanced I get stuck.
(I am open to 'good practice' tips in the comments)
(Is the $id optional? should I remove it for simplicity in the example code?)
Goal:
I am trying to make a schema with an object definition (example_obj) that is recursively used. This object may only have 1 argument (or or and or value). But in the root of the json, I want to add 1 additional property.
json-schema
{
"definitions": {
"example_obj": {
"$id": "#/definitions/example_obj",
"type": "object",
"maxProperties": 1,
"properties": {
"or": {
"$id": "#/definitions/example_obj/properties/or",
"type": "array",
"items": {
"$id": "#/definitions/example_obj/properties/or/items",
"$ref": "#/definitions/example_obj"
}
},
"and": {
"$id": "#/definitions/example_obj/properties/and",
"type": "array",
"items": {
"$id": "#/definitions/example_obj/properties/and/items",
"$ref": "#/definitions/example_obj"
}
},
"value": {
"$id": "#/definitions/example_obj/properties/value",
"type": "string"
}
}
}
},
"type": "object",
"title": "The Root Schema",
"required": ["filter_version"],
"allOf": [
{
"$ref": "#/definitions/example_obj"
},
{
"properties": {
"filter_version": {
"$id": "#/properties/filter_version",
"type": "string",
"pattern": "^([0-9]+\\.[0-9]+)$"
}
}
}
]
}
json which I want to pass validation:
{
"filter_version": "1.0",
"or": [
{
"and": [
{
"value": "subject"
}
]
},
{
"or": [
{
"value": "another subject"
}
]
}
]
}
Issue:
When I try to extend example_obj for the root definition it seems to fail because the example_obj object does not allow more then 1 property by design.
In other words, it appears that every check for the number of argument that I add to example_obj is also performed on the additional property (i.e. filter_version).
Does anyone know where to place this check for 'exactly 1 argument' so that it is not evaluated on the root object?
Attempts:
I tried working with different ways of determining the requirements of example_obj, but with no success. Like with replacing "maxProperties": 1 with:
"oneOf": [
{
"required": [
"or"
]
},
{
"required": [
"and"
]
},
{
"required": [
"where"
]
},
{
"required": [
"where not"
]
}
],
Thanks in advance for any help!!
Checking my schema with the online schema validator.
(In the end I need to validate it in Python, in case it matters)
You can use oneOf instead of maxProperties to get around this.
{
"type": "object",
"properties": {
"filter_version": {
"type": "string",
"pattern": "^([0-9]+\\.[0-9]+)$"
}
},
"required": ["filter_version"],
"allOf": [{ "$ref": "#/definitions/example_obj" }],
"definitions": {
"example_obj": {
"type": "object",
"properties": {
"or": { "$ref": "#/definitions/example-obj-array" },
"and": { "$ref": "#/definitions/example-obj-array" },
"value": { "type": "string" }
},
"oneOf": [
{ "required": ["or"] },
{ "required": ["and"] },
{ "required": ["value"] }
]
},
"example-obj-array": {
"type": "array",
"items": { "$ref": "#/definitions/example_obj" }
}
}
}
P.S. You are using $id wrong. I know there is a tool out there that generates schemas like this and causes this confusion. The way $id is used here is a no-op. It doesn't hurt, but it doesn't do anything other than bloating your schema.

how to set the type of a schema object based on the value of another property?

I have an object (from a 3rd party, so I can't change it) that have a property named "key", and another property called "value" that is optional, and it's type depends on the value of the "key" property.
For instance:
If the key is "comment", the type of value {"Text":"commentValue"}.
If the key is "offset", the type of value is {"seconds":int}.
If the key is "weather", the type of value is {"value": Enum["sun", "clouds", "rain"...]}
Moreover, some of the keys do not have the value property, so the schema should forbid it from appearing with these keys. one of these keys is "standby" (as you can see in my current attempt below)
I've tried manipulating the code samples from this SO answer, but couldn't make it work.
I'm currently attempting to validate output json against my schema attempts using Newtonsoft's JSON Schema Validator - but I can't seem to get the "value" property defined correctly.
This is my code so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"additionalProperties": false,
"required": [
"key",
],
"properties": {
"key": {
"type": "string",
"enum": ["standby", "comment", "offset"]
},
"value" : {
"if": {
"properties": {
"key": {"enum": ["comment"]}
}
},
"then": {
"$ref": "#/definitions/commentValue"
},
"if": {
"properties": {
"key": {"enum": ["offset"]}
}
},
"then": {
"$ref": "#/definitions/offsetValue"
}
}
}
},
"commentValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"text",
],
"properties": {
"text" : {"type" : "string"}
}
},
"offsetValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"seconds",
],
"properties": {
"seconds" : {
"type": "integer",
"format": "int32"
}
}
}
}
}
And this is the error messages I get:
JSON does not match schema from 'then'.
Schema path: #/definitions/offsetValue/then
Property 'text' has not been defined and the schema does not allow additional properties.
Schema path: #/definitions/offsetValue/additionalProperties
Required properties are missing from object: seconds.
Schema path: #/definitions/offsetValue/required
Json examples to validate:
Should fail:
{
"test": {
"key": "comment",
"value": {"seconds":12}
}
}
{
"test": {
"key": "standby",
"value": {"asdf":12}
}
}
Should pass:
{
"test": {
"key": "comment",
"value": {"text":"comment text"}
}
}
{
"test": {
"key": "offset",
"value": {"seconds":12}
}
}
I have changed your JSON Schema so it does what you expect, apart form key of standby as you didn't include that in your schema, and you should be able to replicate the pattern I've created to add new keys as required.
The major issue you had was a false assumption about where to place if/then/else keywords. They are applicator keywords, and so must be applied to the object which you are checking the condition of, and not a properties key value. Because you were using if/then/else in the object which was a value of value, you were applying if/then/else to the value of value rather than test.
You needed your if to apply to test to get the correct scope for checking the key property value.
Here is the resulting fixed schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"required": [
"key"
],
"properties": {
"key": {
"type": "string",
"enum": [
"standby",
"comment",
"offset"
]
}
},
"allOf": [
{
"if": {
"properties": {
"key": {
"const": "comment"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/commentValue"
}
}
}
},
{
"if": {
"properties": {
"key": {
"const": "offset"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/offsetValue"
}
}
}
}
]
},
"commentValue": {
"type": "object",
"additionalProperties": false,
"required": [
"text"
],
"properties": {
"text": {
"type": "string"
}
}
},
"offsetValue": {
"type": "object",
"additionalProperties": false,
"required": [
"seconds"
],
"properties": {
"seconds": {
"type": "integer",
"format": "int32"
}
}
}
}
}
If you want any more help, please feel free to join the JSON Schema slack using the discussion link on the http://json-schema.org site.

How to do json schema validation on collection+json objects?

I would like to validate collection+json objects with schema that have different formats under the same array. For example:
{
"href": "https://example.com/whatnot",
"data": [
{
"name": "foo",
"value": "xyz:123:456"
},
{
"name": "bar",
"value": "8K"
},
{
"name": "baz",
"value": false
}
]
}
Here, the value is one of exactly pattern (\w+:\d+:\d+), one of exactly ([\w\d]+), and one of exactly boolean. There are no other variations.
Is there any way in json schema to have this list checked against these requirements?
I slept overnight and figured how to make the oneOf schema. I tried to use it inside "properties", but it turned out that cannot be done. For the perfect solution, I guess I'd need "explicitOf" kind of method. But, this is good enough for now.
{
"type": "object",
"required": [
"name",
"value"
],
"oneOf": [
{
"properties":
{
"name":
{
"type": "string",
"pattern": "foo"
},
"value":
{
"type": "string",
"pattern": "^(\\w+:\\d+:\\d+)$"
}
}
},
{
"properties":
{
"name":
{
"type": "string",
"pattern": "bar"
},
"value":
{
"type": "string",
"pattern": "^([\\w\\d]+)$"
}
}
},
{
"properties":
{
"name":
{
"type": "string",
"pattern": "baz"
},
"value":
{
"type": "boolean"
}
}
}
]
}