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

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

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
}

How can I validate a json schema array that contains a MIXED type of objects?

I have searched and haven't quite found a solution.
I would like to do a schema as so:
...
"bag": {
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/obj1"},
{"$ref": "#/definitions/obj2"},
{"$ref": "#/definitions/obj3"}
]
},
"required": ["items"],
"minItems": 1
}
...
With objects defined:
...
"definitions": {
"obj1": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
},
"required": ["a"]
}
}
},
"obj2": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"b": {
"type": "string"
}
},
"required": ["b"]
}
}
},
"obj3": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"c": {
"type": "string"
}
},
"required": ["c"]
}
}
}
}
...
Ideally, I would like to validate against a schema that looks like this:
...
"bag": [
{
"obj1": {"a": "test1"}
},
{
"obj3": {"c": "test1"}
}
]
...
In this context, if someone passes obj1 and obj3 into bag. By the schema, obj1 requires property a and obj3 requires property c.
I'm having trouble actually executing this as the validation doesn't seem to enforce correctly.
Any tips? Thanks in advance.
From your current schema and example data, I can't tell exactly what you want, but making an educated guess...
I suspect you want to use oneOf as opposed to anyOf.
anyOf allows you to match multiple subschemas, and it looks like you only want to allow matching one of the subschemas, obj1, 2, or 3.
This would help you debug the issue, but it's not the cause of your always passing validation.
For each definition subschema, you need to add "additionalProperties": false.
Here's the key: JSON Schema is constraints based, meaning anything not constrained is allowed.
additionalProperties restricts the allowed properties of an object to those defined in properties (and patternProperties).
Here's the example schema. You can see it working with your instance here: https://jsonschema.dev/s/MjBUp
{
"$schema": "http://json-schema.org/draft-07/schema",
"definitions": {
"obj1": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
},
"required": ["a"]
}
},
"additionalProperties": false
},
"obj2": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"b": {
"type": "string"
}
},
"required": ["b"]
}
},
"additionalProperties": false
}
},
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/obj1"},
{"$ref": "#/definitions/obj2"}
]
},
"required": ["items"],
"minItems": 1
}

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.

JSON schema for object with either A and B or C required properties

I have two possible JSON objects for one request:
{
"from": "string",
"to": "string",
"text": "string"
}
or
{
"number": "integer",
"text": "string"
}
In both cases "text" property is optional. Other properties are required (either "number, or both "from" and "to").
What will be the correct JSON schema to validate this?
Here is another solution that I think is a bit more clear. The dependencies clause ensures that "from" and "to" always come as a pair. Then the oneOf clause can be really simple and avoid the not-required boilerplate.
{
"type": "object",
"properties": {
"from": { "type": "string" },
"to": { "type": "string" },
"number": { "type": "integer" },
"text": { "type": "string" }
},
"dependencies": {
"from": ["to"],
"to": ["from"]
},
"oneOf": [
{ "required": ["from"] },
{ "required": ["number"] }
]
}
Finally managed to build the correct scheme.
{
"definitions": {
"interval": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": ["from", "to"],
"not": {
"required": ["number"]
}
},
"top": {
"type": "object",
"properties": {
"number": {
"type": "integer"
},
"text": {
"type": "string"
}
},
"required": ["number"],
"allOf": [
{
"not": {
"required": ["from"]
}
},
{
"not": {
"required": ["to"]
}
}
]
}
},
"type": "object",
"oneOf": [
{"$ref": "#/definitions/interval"},
{"$ref": "#/definitions/top"}
]
}