Json-schema field is present and has value - jsonschema

I have a schema like this:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"foo": {
"enum": [
"foo1",
"foo2"
]
},
"bar": {
"type": "number"
},
}
I want bar to be required if foo is present and if it has value "foo1", otherwise it should be forbidden. I was trying something like:
"if": {
"properties": {
"foo": {
"const": "foo1"
}
}
},
"then": {
"required": [
"bar"
]
},
"else": {
"not": {
"required": [
"bar"
]
}
}
However, what happens is if foo is not present, then bar is required and I don't want that. It's as if absence of the field is interpreted as true by the if statement. Is there a way to make one field required only if another optional field is present and has specific value? Something like:
"if": {
allOf: [
{
"properties": {
"foo": {
"const": "foo1"
}
}
},
{
"contains": "foo"
}
]
}
"then": {
"required": [
"bar"
]
},
"else": {
"not": {
"required": [
"bar"
]
}
}
By the way I've found many similar questions here, but none where foo is not required.

The values of the if/then/else keywords are schemas themselves.
What happens if you take the schema value of your if keyword on its own?
What you'll find is, it will validate an empty object successfully without error.
properties only applies subschemas to instance locations where the keys match. If your object has no foo property, you're not doing any validation on the object.
You need to add required to your if statement for this to work how you expect.
"if": {
"properties": {
"foo": {
"const": "foo1"
}
},
"required": ["foo"]
}
See it in action here: https://jsonschema.dev/s/L0rlG

After few more hours I came up with a solution, which I'm going to post here, because it works for earlier versions of json schema, where if/then/else are not available, so someone might need it if they are unable to upgrade, but I accepted Relequestual's answer, because his solution is more elegant for those who have latest version at their disposal
"oneOf":
[
{
"not": {"required": ["foo", "bar"]}
},
{
"required": ["foo"],
"anyOf": [
{
"properties":{
"foo":{"enum": ["foo1"]}
},
"required": ["bar"]
},
{
"properties":{
"foo": {"not": {"enum": ["foo1"]}}
},
"not": {"required": ["bar"]}
}
]}
]

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
}

Make a property required depending on a query parameter with OpenAPI and JSON-Schema

I have a REST API that support for most calls a with query parameter, allowing the user to specify which additional fields they want to load along with the resource’s basic data.
I can describe the query parameter with OpenAPI, and I can describe the format of the response with JSON-Schema. But how can I describe the relation between the parameter and the response?
What I want to describe is:
/api/resource/{id} returns:
{"id": 123, … }
/api/resource/{id}?with=stuff returns:
{"id": 123, …, "stuff": { … }}
I would like this stuff property to be defined as required. Is there a way of doing this?
(What makes this probably even more impossible is that the value of with can be a comma-separated list of properties, but at this point I’m not hoping)
You can't do anything in JSON Schema using out of context data. So, the only way to do what you want is to put the query data into your response.
{
"id": 123,
"query": {},
...
}
{
"id": 123,
"with": ["stuff"],
...
"stuff": { ... }
}
With pure JSON Schema or OpenAPI 3.1, you can use if/then and contains to express the conditional requirement.
{
"type": "object",
"properties": {
"id": { "type": "object" },
"with": { "type": "array", "items": { "type": "string" } },
...
"stuff": { ... }
},
"required": ["id"],
"allOf": [
{
"if": {
"properties": {
"with": { "contains": { "const": "stuff" } }
},
"required": ["with"]
},
"then": { "required": ["stuff"] }
},
... additional conditionals ...
]
}
However, assuming you are on OpenAPI 3.0, you don't have if, then, or contains. It's still possible, but it gets a bit ugly because you need to use a bunch of confusing boolean logic concepts instead.
{
"type": "object",
"properties": {
"id": { "type": "object" },
"with": { "type": "array", "items": { "type": "string" } },
...
"stuff": { ... }
},
"required": ["id"],
"allOf": [
{
"anyOf": [
{
"not": {
"properties": {
"with": {
"not": {
"items": { "not": { "enum": ["stuff"] } }
}
}
},
"required": ["with"]
}
},
{ "required": ["stuff"] }
]
},
... additional conditionals ...
]
}
JSON schema: query string required
schema: {
tags: ["sample"],
summary: "Set query string values to required",
querystring: {
type: "object",
properties: {
name: {
type: "string",
},
address: {
type: "string",
},
},
required: ["name"],
},
},

requiring a param with JSON Schema when another param is not present

update: added requirements
I am trying to implement a JSON schema that works like so…
given the following params: foo, bar, baz, one, and two
One of the following should apply
either no param exists
if one or more params exist then
if foo exists
- then one and two are not required
else if any param(s) other than foo exist(s)
- then one and two are required
of course, if any params are provided that are not in the schema then an appropriate error should be raised.
After a lot of finagling, and inspiration, here is my attempt which doesn't really work
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"oneOf": [
{
"not": {
"properties": { "foo": { "const": "" } },
"required": []
}
},
{ "required": [ "one", "two" ] }
]
}
I also tried the following but was not successful
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"dependencies": {
"not": {
"foo": { "required": ["one", "two"] }
}
}
}
I suspect there might be a hidden requirement or two in here, but here's a solution based on your requirements. Let me know if there's something I'm missing and I'll update my answer.
Most of your requirements are the default behavior in JSON Schema, so you don't have to do anything.
either no param exists
All properties are optional by default, so nothing to do here
if foo exists - then one and two are not required
Again, optional is the default, so nothing to do here
if one or more params exist then [and] any param(s) other than foo exist(s) - then one and two are required
The best way to express this is with if/then. If "foo" is optional, it's a little complicated to express, "any param(s) other than foo", so I put that in definitions to make it a bit easier to read.
if any params are provided that are not in the schema then an appropriate error should be raised.
The additionalProperties keyword takes care of this. You just need to add "one" and "two".
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" },
"baz": { "type": "string" },
"one": {},
"two": {}
},
"additionalProperties": false,
"if": { "$ref": "#/definitions/has-property-other-than-foo" },
"then": { "required": ["one", "two"] },
"definitions": {
"has-property-other-than-foo": {
"if": { "required": ["foo"] },
"then": { "minProperties": 2 },
"else": { "minProperties": 1 }
}
}
}

How to specify if a property should not exist or contain null?

A year ago I've asked how to set the type of a schema object based on the value of another property? which I've got a great answer for, and I've been using that schema ever since.
Now the source data have changed - and the schema is failing under the following circumstances:
The source data contains many properties, however only two of them are relevant for this question: "key" and "value" - the type of "value" depends on the value of "key" -
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"...]}
Some of the keys do not have the value property, so the schema should forbid it from appearing with these keys - for instance, if the key is "standby" the value property should not appear at all - which is what my current schema is doing good.
However, now the data source have changed and I get "value" :{} as a part of my Json where once it was omitted - and the current schema does not allow it.
So my question is - how do I allow one of these two options? I've tried any combination of anyOf I could think of, but failed miserably - the Newtonsoft.Json.Schema.JSchema failed to parse the text.
Here's a simplified version of the schema I'm currently using:
{
"$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": [
"comment",
"offset",
"standby",
"status_unsure",
"status_ok"
]
}
},
"allOf": [
{
"if": {
"properties": {
"event": {
"enum": [
"standby",
"status_unsure",
"status_ok"
]
}
}
},
"then": {
"properties": {
"value": false
}
}
},
{
"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"
}
}
}
}
}
Here are some of the things I've tried:
"then": {
"properties": {
"anyOf": [
{ "value": false },
{ "value": null }
]
}
}
"then": {
"anyOf": [
{
"properties": {
{ "value": false }
},
"properties": {
{ "value": null }
}
]
}
"then": {
"properties": {
"value":
"anyOf": [false, null ]
}
}
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}
}
}
{
"test": {
"key": "standby"
}
}
{
"test": {
"key": "standby",
"value": {}
}
}
Note the last example - the value property is an empty object - that also should pass but fails with the current schema since the value property should not exist at all for this key.
You're close, but not quite. You have to remember that the allOf is an array of subschemas (JSON Schemas). (Null isn't a valid schema, so you might have got some "not a valid schema" errors.)
As such, consider this modified subschema from allOf[0]...
{
"if": {
"properties": {
"key": {
"enum": [
"standby",
"status_unsure",
"status_ok"
]
}
}
},
"then": {
"properties": {
"value": {
"type": [
"object"
],
"additionalProperties": false
}
}
}
}
You can test it here: https://jsonschema.dev/s/EfNI1
The if block remains the same. (Although I've corrected what I assume was a mistake in using event rather than key in your simplification from the real schema.)
The then block needs to define that the object (already checked by definitions.test) has a key of value, where the value of value is an object, and has no properties (aka an empty object).
To achive "an empty object", you need to use additionalProperties.
additionalProperties applies its value subschema to all properties that exist that haven't been defined in properties or that match the keys (regexes) from patternProperties.
false as a schema always fails validation.
additionalProperties without properites applies to ALL properties, and therefore with a value of false, validates "is an empty object".

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.