JSON Schema (v04) anyOf with dependencies - jsonschema

I have a JSON that can have either an IBAN account number, in which case a BIC is required, or an account_number or both of them.
So, this is valid (only account_number):
"bankaccount_data": [
{
"bic": null,
"iban": null,
"account_name": "Bankgiro",
"account_number": "12345-6789",
"bank_name": "Bankgiro",
"type": "Bankgiro"
}
]
And this is valid with iban and bic:
"bankaccount_data": [
{
"bic": "BANKBIC",
"iban": "SE0123456789",
"account_name": "Bankgiro",
"account_number": null,
"bank_name": "Bankgiro",
"type": "Bankgiro"
}
]
Even this with both account_number and iban/bic:
"bankaccount_data": [
{
"bic": "BANKBIC",
"iban": "SE0123456789",
"account_name": "Bankgiro",
"account_number": "12345-6789",
"bank_name": "Bankgiro",
"type": "Bankgiro"
}
]
My problem is with requiring BOTH iban and bic if either/or exists. I have this schema which is not doing that but should "illustrate" my need:
"bankaccount_data": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"account_name": { "type": "string" },
"bank_name": { "type": [ "string", "null" ] },
"type": { "type": "string" }
},
"required": [ "type" ],
"anyOf": [
{
"type": "object",
"properties": {
"bic": { "type": "string" },
"iban": { "type": "string" }
},
"required": [ "bic", "iban" ],
"dependencies": {
"iban": [
"bic"
],
"bic": [
"iban"
]
}
},
{
"type": "object",
"properties": {
"account_number": { "type": "string" }
},
"required": [ "account_number" ]
}
]
}
]
}

I wasn't quite sure, but I think you're asking for, if iban or bic is present, then the other must also be present.
You had the right ideas here. However it's complicated by the fact that your values can be null as opposed to simply not present.
dependencies only checks the properties are present, not anything to do with their value. null is still a value. dependencies cannot help you in your situation, as you always expect the keys to be present in the object.
First let's look at the solution.
{
"anyOf": [
{
"properties": {
"bic": { "type": "string" },
"iban": { "type": "string" },
"account_number": { "enum": [null]}
},
"required": [
"iban",
"bic"
]
},
{
"required": [
"account_number"
],
"properties": {
"bic": { "enum": [null] },
"iban": { "enum": [null] },
"account_number": { "type": "string" }
}
}
]
}
https://jsonschema.dev/s/dg0CY
The reason you need this sort of duplication, is you need to fully express the condition that your checking.
The values of an anyOf array are subschemas. The values are full schemas on their own.
Taking your values for anyOf, anyOf[0] does express the constraint that your looking for, however anyOf[1] says nothing about iban or bic, and so "any of" the schemas is considered valid. Each value in anyOf is not aware of the contense of the other; only the results are combined.
JSON Schema is constraints based, so anything NOT expressed, is allowed.
In the solution I've provided above, each schema in anyOf checks fully for the required condition.
For your example, it works in draft-07 the same as draft-04, so the demo is the same.

Related

JSON-schema exclude properties (opposite to required properties)

in JSON-schema, one can require certain keys in an object, see this example, taken from the json schema docs:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"address": { "type": "string" },
"telephone": { "type": "string" }
},
"required": ["name", "email"]
}
I need the opposite: Is there a way to forbid or prevent a certain key from being in an object? Specifically, I want to prevent users from having an empty key inside an object:
{
"": "some string value"
}
You can exclude certain keys by name with:
{
"not": {
"anyOf": [
{ "required": [ "property1" ] },
{ "required": [ "property2" ] },
{ "required": [ "property3" ] },
...
]
}
https://json-schema.org/understanding-json-schema/reference/combining.html
Next to excluding keys with the not syntax (see answer by Ether), it is possible to achieve the goal with property-names.
In the following example, all keys have to map to a string URI. We furthermore exclude all keys that are not alphanumeric (via pattern), or don't have a specific length (via minLength). Other than that, we have no restrictions on keys.
"SomeKeyToBeValidated": {
"type": "object",
"properties": { },
"propertyNames": {
"type": "string",
"minLength": 1,
"pattern": "^[a-zA-Z0-9]*$"
},
"additionalProperties": {
"type": "string",
"format": "uri"
}
}

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 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.

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.