Json Schema: Require a property only when a specific property is present in a deep nested object - jsonschema

I need to build a json schema (draft 4) that requires a property based on the presence of a property in another nested object. I already searched and tried a lot of things (anyOf, oneOf, not, dependencies) with no luck.
Maybe this is not possible to in json schema?
This is my simplified schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"required": ["dog"],
"properties": {
"dog": {
"type": "object",
"required": ["bananas"],
"properties": {
"bananas": { "$ref": "bananas.json" },
"thing": {
"type": "object",
"properties": {
"total": { "type": "string" }
}
}
}
}
}
}
And this is bananas.json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"required": ["banana"],
"definitions": {
"non-empty-string": {
"type": "string",
"minLength": 1
}
},
"properties": {
"banana": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["unit"],
"properties": {
"unit": { "type": "string" },
"thing": {
"type": "object",
"anyOf": [
{ "required": [ "tax_transfers" ] },
{ "required": [ "tax_retentions" ] }
],
"properties": {
"tax_transfers": {
"type": "object",
"required": ["tax_transfer"],
"properties": {
"tax_transfer": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"rate": { "type": "string" }
}
}
}
}
},
"tax_retentions": {
"type": "object",
"required": ["tax_retention"],
"properties": {
"tax_retention": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"rate": { "type": "string" }
}
}
}
}
}
}
}
}
}
}
}
}
I need that when one or more objects in the array have a 'thing' property (at bananas -> banana -> thing).
Then the property 'thing' at (dog -> thing) should be required.
Any help would be really appreciated.

You need two things to express your constraint. The first is "contains" and the other is "implication". I've organized each in the definitions section.
Contains
The items keyword allows us to require that all items in an array are valid against a schema. If it is not true that all of the items in the array are not valid against the schema, then we know that at least one item is valid.
{
"not": {
"items": { "not": { ... schema ... } }
}
}
If you are able to upgrade to JSON Schema draft-06, a contains keyword was added to make this much easier.
{
"contains": { ... schema ... }
}
Implication
Implication allows you to do something like a conditional. The condition schema implies the constraint schema if either the condition is true, or the constraint is true (or both are true). It's effectively the same as saying, if the condition is true then the constraint must also be true.
{
"anyOf": [
{ "not": { ... condition schema ... } },
{ ... constraint schema ... }
]
}
JSON Schema draft-07 adds the if-then-else keywords in attempt to address this case better. I personally dislike the way this was done enough that I'll stick with the implication pattern for this kind of thing, but here it is in case you want to try it.
{
"if": { ... schema ... },
"then": { ... schema ... },
"else": { ... schema ... }
}
All together
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"required": ["dog"],
"properties": {
"dog": {
"type": "object",
"required": ["bananas"],
"properties": {
"bananas": { "$ref": "bananas.json" },
"thing": { "type": "object" }
}
}
},
"allOf": [
{ "$ref": "#/definitions/banana-things-implies-dog-things" }
],
"definitions": {
"banana-has-things": {
"properties": {
"dog": {
"properties": {
"bananas": {
"properties": {
"banana": {
"not": {
"items": { "not": { "required": ["things"] } }
}
}
}
}
}
}
}
},
"banana-things-implies-dog-things": {
"anyOf": [
{ "not": { "$ref": "#/definitions/banana-has-things" }},
{
"properties": {
"dog": { "required": ["things"] }
}
}
]
}
}
}

Related

Add additional properties to json array member schema

I'm trying to create 2 separate schemas for some json files which serve as scripts.
One schema should contain all commands possible, while another one should only contain a subset of commands.
My thinking was I create minimal schema first:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"$schema": {},
"commands": {
"type": "array",
"items":
{
"title": "One step of the migration",
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"properties": {
"simple_command_1": {"type": "object"},
"simple_command_2": {"type": "object"},
}
}
}
}
}
which would validate json files like this:
{
"$schema": "../../migration_schema_v1.json",
"commands": [
{"simple_command_1": {}},
{"simple_command_1": {}},
{"simple_command_2": {}},
{"simple_command_2": {}},
]
I'm trying to avoid using the word extend but... I want to be able to create a second schema that has every command the first one has and an additional advanced_command_3. So I created this:
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "/migration_schema_v1.json#/",
"type": "object",
"properties": {
"foo": {
"type": "object",
"title": "this does work"
},
"commands": {
"items":
{
"properties": {
"advanced_command_3": {"type": "object"}
}
}
}
}
}
I'm able to refer this second schema, and it does inherit everything from the first one, but I'm not really able to add any properties (commands)
I was however able to add additional properties in the root, next to "commands". See "foo" property.
Using allOf[] or $ref in the sub-property doesn't seem to make a difference.
Am I getting this all wrong?
Thanks!
I would actually break this into three schemas. The first contains the primary definition for your data, including all of the valid commands.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/definitions",
"type": "object",
"properties": {
"$schema": {},
"commands": {
"type": "array",
"items":
{
"title": "One step of the migration",
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"properties": {
"simple_command_1": {"type": "object"},
"simple_command_2": {"type": "object"},
"advanced_command_3": {"type": "object"}
}
}
}
}
}
The other two are your simple and advanced schemas. They work by defining that only the possibilities of the commands you want are required then referencing back to the definition schema to get the rest of the requirements.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/simple",
"$ref": "definitions",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] }
]
}
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/advanced",
"$ref": "definitions",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] },
{ "required": ["advanced_command_3"] }
]
}
}
}
}
I think this setup contains minimal repetition.
At this point you might be able to get away with what you're wanting to do with the extends thing. You'd have to extract the commands requirements in the simple schema into a $defs keyword and reference them from the advanced schema.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/simple",
"$defs": {
"simpleCommands": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] }
]
}
},
"$ref": "definitions",
"type": "object",
"properties": {
"commands": {
"items": { "$ref": "#/$defs/simpleCommands" }
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/advanced",
"$ref": "definitions",
"type": "object",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "$ref" : "simple#/$defs/simpleCommands" },
{ "required": ["advanced_command_3"] }
]
}
}
}
}
I haven't tested this though.

Is there a way to have a rule in one object that references properties from a different object?

I could not find any examples of this so I'm assuming it's not possible, but want to confirm.
I have a main schema that references other schemas:
https://www.jsonschemavalidator.net/s/4aLvXa4I
{
"$defs": {
"mainSchema": {
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"type": "object",
"properties": {
"objectOne": {
"$ref": "#/$defs/objectOne"
},
"objectTwo": {
"$ref": "#/$defs/objectTwo"
}
}
},
"objectOne": {
"type": "object",
"properties": {
"checkThisValue": {
"type": "string",
"enum": [
"one",
"two",
"three"
]
}
}
},
"objectTwo": {
"type": "object",
"properties": {
"whenSettingThisValue": {
"type": "string",
"enum": [
"A",
"B",
"C"
]
}
}
}
},
"$ref": "#/$defs/mainSchema"
}
I want to define this rule:
IF objectOne.checkThisValue == one
THEN objectTwo.whenSettingThisValue MUST == A
Same for two=>B and three=>C
Is this possible somehow? How do I reference objectOne properties inside of objectTwo?
Edit
I tried to create an if rule for objectTwo that references objectOne here, but my syntax is wrong because it's not working. whenSettingThisValue is set to C and it's saying valid when it should be invalid: https://www.jsonschemavalidator.net/s/NNjEIhWW
"objectTwo": {
"type": "object",
"properties": {
"whenSettingThisValue": {
"type": "string",
"enum": [
"A",
"B",
"C"
]
}
},
"if": {
"properties": {
"objectOne": {
"checkThisValue": {
"const": "one"
}
}
}
},
"then": {
"properties": {
"objectTwo": {
"whenSettingThisValue": {
"const": "A"
}
}
}
}
}
},
I also tried using a oneOf rule in mainSchema where both the subschemas are used, but it is not working either (this example should be invalid): https://www.jsonschemavalidator.net/s/WkmMasDC
"mainSchema": {
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"type": "object",
"properties": {
"objectOne": {
"$ref": "#/$defs/objectOne"
},
"objectTwo": {
"$ref": "#/$defs/objectTwo"
}
},
"oneOf": [
{
"type": "object",
"properties": {
"objectOne": {
"checkThisValue": {
"const": "one"
}
},
"objectTwo": {
"whenSettingThisValue": {
"const": "A"
}
}
}
}
]
},
Solution
Per answer my OneOf rule was malformed. This correctly checks a value in one object is set in response to another. I thought because I was referencing them in another schema with $ref I would have to do something special, but I don't.
https://www.jsonschemavalidator.net/s/HcVhrShk
"oneOf": [
{
"type": "object",
"properties": {
"objectOne": {
"properties": {
"checkThisValue": {
"const": "one"
}
}
},
"objectTwo": {
"properties": {
"whenSettingThisValue": {
"const": "A"
}
}
}
}
},
{
"type": "object",
"properties": {
"objectOne": {
"properties": {
"checkThisValue": {
"const": "two"
}
}
},
"objectTwo": {
"properties": {
"whenSettingThisValue": {
"const": "B"
}
}
}
}
}
]
},
Yes you can. There is an if/then/else construct which takes schemas as its arguments, so you can define a rule "if property A exists with value X, then property B must exist with value Y" etc.
There are some examples here:
https://json-schema.org/understanding-json-schema/reference/conditionals.html#if-then-else
edit:
in your re-edit, change the oneOf clause to this:
"oneOf": [
{
"type": "object",
"properties": {
"objectOne": {
"properties": {
"checkThisValue": {
"const": "one"
}
}
}
}
},
{
"type": "object",
"properties": {
"objectTwo": {
"properties": {
"whenSettingThisValue": {
"const": "A"
}
}
}
}
}
]

Referring inner properties in the IF condition - JSON Schema

The following is a sample schema to depict the issue
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"person": {
"type": "object",
"properties": {
"age": {
"type": "string"
}
}
}
},
"properties": {
"child": {
"$ref": "#/definitions/person"
}
},
"required": [
"child"
],
"if": {
"properties": {
"person/age": {
"const": "3"
}
}
},
"then": {
"properties": {
"guardian": {
"$ref": "#/definitions/person"
}
},
"required": [
"guardian"
]
}
}
Is there a way to refer age inside the person object?
{"child":{"age":"3"}}. Should fail as guardian tag is missing
The above data should fail as the guardian object is missing.
Remember that if is just a regular schema validating against the instance. Just nest your properties like you would with any nested object structure.
{
"type": "object",
"properties": {
"child": {
"type": "object",
"properties": {
"age": { "const": "3" }
},
"required": ["age"]
}
},
"required": ["child"]
}
Note that the type and required keywords are necessary to not inadvertently trigger the then schema. For example, with out them, these would cause the then to trigger when you probably didn't want it to.
{}
{ "child": null }
{ "child": {} }

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
}

jsonschema with custom property

I want to define a JsonSchema with a customProperty in it, this property follows some rules, so in order to validate those, I need to define a JsonSchema that will validate it.
So far I've managed to describe it properly, but it only works for the first level of attributes, and I want it to be recursive...
From my comprehension it should work, I probably made a mistake I'm unable to see and at this point I don't know if it's a bug, impossible, or stupid...
I believe redefining every type should be a possibility , but obviously I'd rather not.
Here is an example of the Json I want to validate
{
"title": "TheObject",
"type": "object",
"properties": {
"aString": {
"type": "string",
"myCustomProperty": {}
},
"anObjet": {
"type": "object",
"myCustomProperty": {},
"properties": {
"anotherObject": {
"type": "object",
"myCustomProperty": {}, //if this line is removed it still validates which I don't want
"properties": {}
}
}
}
}
}
and here is the JsonSchema I've done so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {"type": "string"},
"type": {"type": "string","enum": ["object"]},
"properties": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"definitions": {
"Field": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"myCustomProperty": {
"$ref": "#/definitions/myCustomProperty"
},
"patternProperties": {
"^(?!myCustomProperty).*": {
"$ref": "#/definitions/Field"
}
}
},
"required": [
"type",
"myCustomProperty"
]
},
"myCustomProperty": {
//Some rules
}
}
}
I found a solution, I wasn't far from what I wanted after all.
In my definition of "Field", I am describing an object that defines an object, and I was missing the "properties" field. In which I had to put my recursive reference.
The right jsonSchema is the following :
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"object"
]
},
"properties": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"definitions": {
"Field": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"myCustomProperty": {
"$ref": "#/definitions/myCustomProperty"
},
"properties": { <==================== here
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"required": [
"type",
"myCustomProperty"
]
},
"myCustomProperty": {/*rules*/}
}
}
So far it works as expected, but if someone has a more elegant suggestion please share!