Mutually exclusive property groups - jsonschema

Suppose I have an object with four possible properties: a, b, c, d. a and b can only appear together (i.e., a appears if and only if b appears). If a and b appear, c cannot appear (that is, a/b and c are mutually exclusive). If a and b do not appear, c may appear (but is not required to). d can appear in any combination with a/b, c, or on its own. No properties other than a, b, c, or d may appear at all.
How do I express this as a jsonschema? I suspect I could use some combination of oneOf and required, but I can't figure out the proper incantation.

You can phrase your constraints as:
either: both "a" and "b" are present, and "c" is not present
or: neither "a" nor "b" is present. ("c" may or may not be present)
Saying "neither" in the second point is a bit verbose. Here, we've expressed it using allOf/not. (Note: you can't factor them into a single required clause here, because you need a separate not for each one.)
{
"oneOf": [
{
"required": ["a", "b"],
"not": {"required": ["c"]}
},
{
"allOf": [
{
"not": {"required": ["a"]}
},
{
"not": {"required": ["b"]}
}
]
}
]
}
Alternative structure
There's also another way to say "neither", which is actually to use oneOf again. Since you must pass exactly one of a oneOf clause, if one of the entries is {} (passes everything), then all the other options are banned.
While it's slightly more concise, it's possibly slightly less intuitive to read:
{
"oneOf": [
{
"required": ["a", "b"],
"not": {"required": ["c"]}
},
{
"oneOf": [
{},
{"required": ["a"]},
{"required": ["b"]}
]
}
]
}

Another alternative is to use the schema dependencies declaration:
"dependencies": {
"c": {
"allOf": [
{
"not": { "required": [ "a" ] }
},
{
"not": { "required": [ "b" ] }
}
]
},
"a": {
"allOf": [
{
"not": { "required": [ "c" ] }
},
{
"required": [ "b" ]
}
]
},
"b": {
"allOf": [
{
"not": { "required": [ "c" ] }
},
{
"required": [ "a" ]
}
]
}
}

Related

Json schema for either A or B

I want to write a schema specification which allows for either property A or B. For example, both of
{
"foo" : "bar",
"A" : "something"
}
{
"foo" : "bar",
"B" : "something else"
}
should be legal, but
{
"foo" : "bar",
"A" : "something",
"B" : "something else"
}
should be illegal. What's the right way to do that with 2020-12/schema
That's possible with the oneOf operator. You can keep your property definitions outside of the oneOf, so you don't have to repeat yourself:
"type": "object",
"oneOf": [
{ "required": [ "A" ] },
{ "required": [ "B" ] }
],
"properties": {
"A": { ... },
"B": { ... },
"foo": { ... }
}

Deeply nested unevaluatedProperties and their expectations

I have been working on my own validator for JSON schema and FINALLY have most of how unevaluatedProperties are supposed to work,... I think. That's one tricky piece there! However I really just want to confirm one thing. Given the following schema and JSON, what is the expected outcome... I have tried it with a https://www.jsonschemavalidator.net and gotten an answer, but I was hoping I could get a more definitive answer.
The focus is the faz property is in fact being evaluated, but the command to disallow unevaluatedProperties comes from a deeply nested schema.
Thoguhts?
Here is the schema...
{
"type": "object",
"properties": {
"foo": {
"type": "object",
"properties": {
"bar": {
"type": "string"
}
},
"unevaluatedProperties": false
}
},
"anyOf": [
{
"properties": {
"foo": {
"properties": {
"faz": {
"type": "string"
}
}
}
}
}
]
}
Here is the JSON...
{
"foo": {
"bar": "test",
"faz": "test"
}
}
That schema will successfully evaluate against the provided data. The unevaluatedProperties keyword will be aware of properties evaluated in subschemas of adjacent keywords, and is evaluated after all other applicator keywords, so it will see the annotation produced from within the anyOf subschema, also.
Evaluating this keyword is easy if you follow the specification literally -- it uses annotations to decide what to do. You just need to make sure that all keywords either produce annotations correctly or propagate annotations correctly that were produced by other keywords, and then all the information is available to generate the correct result.
The result produced by my implementation is:
{
"annotations" : [
{
"annotation" : [
"faz"
],
"instanceLocation" : "/foo",
"keywordLocation" : "/anyOf/0/properties/foo/properties"
},
{
"annotation" : [
"foo"
],
"instanceLocation" : "",
"keywordLocation" : "/anyOf/0/properties"
},
{
"annotation" : [
"bar"
],
"instanceLocation" : "/foo",
"keywordLocation" : "/properties/foo/properties"
},
{
"annotation" : [],
"instanceLocation" : "/foo",
"keywordLocation" : "/properties/foo/unevaluatedProperties"
},
{
"annotation" : [
"foo"
],
"instanceLocation" : "",
"keywordLocation" : "/properties"
}
],
"valid" : true
}
This is not an answer but a follow up example which I feel is in the same vein. I feel this guides us to the answer.
Here we have a single object being validated. But the unevaluated command resides in two different schemas each a part of a different "adjacent keyword subschemas"(from the core spec http://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.11)
How should this be resolved. If all annotations must be evaluated then in what order do I evaluate? The oneOf first or the anyOf? According the spec an unevaluated command(properties or items) generate annotation results which means that that result would affect any other unevaluated command.
http://json-schema.org/draft/2020-12/json-schema-core.html#unevaluatedProperties
"The annotation result of this keyword is the set of instance property names validated by this keyword's subschema."
This is as far as I am understanding the spec.
According to the two validators I am using this fails.
Schema
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": {
"foo": {
"type": "string"
}
},
"oneOf": [
{
"properties": {
"faz": {
"type": "string"
}
},
"unevaluatedProperties": true
}
],
"anyOf": [
{
"properties": {
"bar": {
"type": "string"
}
},
"unevaluatedProperties": false
}
]
}
Data
{
"bar": "test",
"faz": "test",
}

json-schema with additional properties of type x and at least one

Question:
Is there a way to tell the json-schema that I want additional properties of type x and at least one?
short explanation (based on the code-block below):
I want to have a json-file with one required item, a.
b is optional and I need minimum one additional item.
The name of the additional item has to be flexible.
For that reason I'm not able to give it a specific name and mark it as required.
And because b is optional, I'm not able to use 'minProperties'
something like: (doesn't exist)
"additionalProperties": {
"type": "string",
"minAdditionalProperties": 1
}
unfinished json-schema
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
}
},
"required": ["a"],
"additionalProperties": {
"type": "string"
}
}
expected result:
b is always optional
invalid:
{
"a": 1
}
----
{
"a": 1,
"b": 2
}
valid:
{
"a": 1,
"x": "2"
}
----
{
"a": 1,
"x1": "2",
"x2": "4"
}
Is something like this even possible?
And because b is optional, I'm not able to use 'minProperties'
Why not? Surely minProperties: 2 will get you what you need: 'a' is required, so one additional property is needed.
If you want one additional property in addition to the optional property 'b', then you can use an if/then/else:
"if": {
"required": [ "b" ]
},
"then": {
"minProperties": 3
}
"else": {
"minProperties": 2
}

JSON Schema: Conditionally require property depending on several properties' values

I want to validate a JSON file using JSON schema, several "properties" should be required depending on what values some other properties have.
Example
Having properties "A", "B", "C", and "D"
if "A" has value "foo", C is required
if "B" has value "foo", D is required
if both "A" and "B" each have value "foo", both C and D are required
else, nothing is required
I have seen a very helpful answer here: https://stackoverflow.com/a/38781027/5201771 --> there, the author addresses how to solve this problem for the case of a single property (e.g., only "A" has value "foo", so require "C").
However, I currently don't see how to extend that answer to my case, where several properties determine the outcome.
Example Files
for illustration, I supply some files that should pass or fail the validation:
should pass:
{
"A": "bar"
"B": "baz"
}
{
"A": "foo"
"C": "some value"
}
{
"A": "bar"
"B": "foo"
"D": "some value"
}
should fail:
{
"A": "foo"
"B": "foo"
"D": "some value"
}
You can combine conditionals a number of ways, but combining them with allOf is usually the best way.
{
"type": "object",
"properties": {
"A": {},
"B": {},
"C": {},
"D": {}
},
"allOf": [
{ "$ref": "#/definitions/if-A-then-C-is-required" },
{ "$ref": "#/definitions/if-B-then-D-is-required" }
],
"definitions": {
"if-A-then-C-is-required": {
"if": {
"type": "object",
"properties": {
"A": { "const": "foo" }
},
"required": ["A"]
},
"then": { "required": ["C"] }
},
"if-B-then-D-is-required": {
"if": {
"type": "object",
"properties": {
"B": { "const": "foo" }
},
"required": ["B"]
},
"then": { "required": ["D"] }
}
}
}

Validate objects in JSON array with Ajv

I am trying to validate if the "actions" array has specific objects. Each object can have their own properties, depending on the type property. The actions array can have multiple occurences from these type of objects, not just one.
My JSON:
{
"actions": [
{
"type": "X",
"a": 1,
"b": 2,
"c": 3
},
{
"type": "Y",
"d": 1,
"e": 2,
"f": 3
}
]
}
In this example the object that has type = X has required properties as a, b, c. The object type Y in the array has required properties d, e, f.
I am trying to validate this with the switch keyword:
{
"type": "object",
"required": [
"actions"
],
"properties": {
"actions": {
"type": "array",
"items": {
"switch": [
{
"if": {
"properties": {
"type": {
"pattern": "^X$"
}
}
},
"then": {
"required": [
"a",
"b",
"c"
]
}
},
{
"if": {
"properties": {
"type": {
"pattern": "^Y$"
}
}
},
"then": {
"required": [
"d",
"e",
"f"
]
}
}
]
}
},
}
}
But still I could not figure out how to use the switch keyword to validate objects of arrays where each object type is specified by a property in the object so the object type can be identified with a property.
Your schema looks almost ok, you may need one last {then: false} subschema inside "switch", as without it if the "type" property is neither 'X' nor 'Y' the validation will succeed.
Also, instead of using {pattern: '^X$'} you can use {enum: ['X']} or even {constant: 'X'} (in draft-06 it is "const"); instead of using switch you can use "if/then/else" (from ajv-keywords, it is likely to be included in draft-07) or even "select", that may be more suitable for such scenario.
Maybe you can clarify the last question, I don't think I understand.