JsonSchema: How make sure that an object requires one/specific properties? - jsonschema

I have an object to define, that needs needs one key or another specific key.
Basically a simple or: if(key1 || key2){ -> valid} else {-> invalid}
Now I know of the "require" keyword, but that it isn't able to require keys conditionally as far as I know.
In my case I want to exactly key1 or key2 to exists.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/my_schema",
"type": "object",
"properties": {
"key1": {
"type": "string"
},
"key2": {
"type": "array",
"items": {
"type": "string"
}
},
"key3": {
"type": "array",
"items": {
"type": "string",
"minItems": 1
}
},
"key4": {
"type": "object",
"properties": {
"begin": {
"type": "number"
},
"end": {
"type": "number"
}
}
}
},
"required": [
"key3", "key4"
]
}
If possible, I look for something where I could define an object conditionally like this e.g.:
"required": {
"key1": if(someConditions),
"key2": if(someOtherConditions)
}
or even this:
"porperties":{
"key1":{
"type": someType,
"required": if(someCondition)
}
}
But right now I simply want to make sure that key1 or key2 are required and that the schema is invalid if both are missing.

To validate at least one of two attributes must be required, you could use anyOf blocks like this.
"properties": {...},
"required":[
"key3",
"key4"
],
"anyOf": [
{
"required": [
"key1"
]
},
{
"required": [
"key2"
]
}
]
To validate availability based on some condition you could use if then else keywords. For example,
If key1 = "1", then key2 is required. Otherwise, key3 is required
"if": {
"key1": {
"const": "1" // some simple condition
}
},
"then": {
"required": [
"key2"
]
},
"else": {
"required": [
"key3"
]
}

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