JSON Schema - anyOf within conditional? - conditional-statements

I am trying to define a JSON schema with conditionals. I built an MVE which already doesn't work as I expect it.
The object I want to validate is:
{
"keiner": false,
"abdominal": true,
"zervikal": false
}
The conditional rule is simple. When "keiner" is true, both other values have to be false. If "keiner" is false, at least one of the other two has to be true.
I wrote this schema:
{
"type": "object",
"properties": {
"keiner": { "type": "boolean" },
"abdominal": { "type": "boolean" }
},
"if": {
"properties": {
"keiner": { "const": true }
}
},
"then": {
"properties" : {
"abdominal": { "const": false },
"zervikal": {"const": false }
}
},
"else": {
"properties": {
"anyOf": [
{ "abdominal": { "const": true } },
{ "zervikal": { "const" : true } }
]
}
}
}
But the Newtonsoft online validator gives the error message
Unexpected token encountered when reading value for 'anyOf'. Expected StartObject, Boolean, got StartArray.
for the line in which ´anyOf´ starts. This confuses me, as all examples I can find show anyOf followed by an array of options.
So what am I doing wrong? Why cannot I have a startArray after anyOf, and how do I write the schema correctly?

I guess this is the schema you are looking for:

Related

Can JSON Schema if statements handle nested $refs?

I have a JSON Schema using draft 2020-12 and I am trying to use an if-else subschema to check that a particular property exists based on the value of another property. Below is the if statement I am currently using. There are more properties but I have have omitted them for the sake of brevity. They are identical except the type of the property in the then statement is different. They are all wrapped in an allOf array:
{
"AValue": {
"allOf": [
{
"if": {
"$ref": "#/$defs/ValueItem/properties/dt",
"const": "type1"
},
"then": {
"properties": {
"string": { "type": "string" }
},
"required": ["string"]
}
}
]
}
}
The #/$defs/ValueItem/properties/dt being referred to is here:
{
"ValueItem": {
"properties": {
"value": {
"$ref": "#/$defs/AValue"
},
"dt": {
"$ref": "#/$defs/DT"
}
},
"additionalProperties": false
}
}
#/$defs/DT is here:
{
"DT": {
"type": "string",
"enum": [
"type1",
"type2",
"type3",
"type4"
]
}
}
I expected that when dt is encountered in a JSON instance document, the validator will check if the value of dt is type1 and then validate that an additional property called string is also present and is of type string. However, what actually happens is the validator complains that "Property 'string' has not been defined and the schema does not allow additional properties".
I assume that this is because the condition in the if statement evaluates to false so the subschema is never applied. However, I am unsure why that would be as I followed the example here when creating the if-then-else block. The only thing I can think of that is different is the use of $ref which I have in my schema but it is not in the example.
I found this answer which makes me think that it is possible to use $ref in an if statement but is it possible to use a ref that points to another ref or am I thinking about it incorrectly?
I have also tried removing the $ref from the if statement like below but it still doesn't work. Is it because of the $ref in the properties?
{
"AValue": {
"properties": {
"dt": {
"$ref": "#/$defs/DT"
}
},
"required": [
"dt"
],
"allOf": [
{
"if": {
"properties": {
"dt": {
"const": "type1"
}
}
},
"then": {
"properties": {
"string": {
"type": "string"
}
}
}
}
]
}
}
The problem is not cascading the $ref keywords. The const keyword at the if statement is not applied to the target of the $ref, but to the current location in the JSON input data. In this case to "AValue". To check if the property "dt" is of value "type1" at this point, you would need an if like this (simple solution with no $ref):
"if": {
"properties": {
"dt": {
"const": "type1"
}
},
"required": [ "dt" ]
}
Edit: Showing complete JSON Schema and error in JSONBuddy with $ref:

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",
}

How to enforce only one property value to true in an array (JSON Schema)

I am trying to have JSON validation based on the following input:
{
"elements":[
{
"..."
"isSelected": true
},
{
"..."
"isSelected": false
},
{
"..."
"isSelected": false
}
]
}
The input is going to be valid if and only if we have "isSelected" set to "true" (and all the rest set to "false"). Can't have "isSelected: true" more than once (and all the rest need to be "false").
Tried with the following:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"element":{
"type": "object",
"properties": {
"isSelected": {
"type": "boolean"
}
}
}
},
"properties": {
"elements": {
"type": "array",
"items": {
"$ref": "#/definitions/element"
},
"oneOf": [
{
"isSelected": true
}
]
}
},
}
unfortunately I don't think this is possible with json schema draft 7. the newest draft (2019-09) features the maxContains keyword, which would be able to validate this, but tooling for this draft is sparse so far. I don't know the tooling you're using, but if you are able to use 2019-09, the schema for 'elements' would look something like:
{
"type": "array",
"contains": {
"properties": {
"isSelected": {"const": true}
}
},
"maxContains": 1
}
oneOf isn't what you're looking for, for this - it checks that one of a set of schemas validates against the instance, not whether one of a set of instances validates against a schema.
This is not currently supported, but you may be interested in this proposal which intends to add a keyword to support key-based item uniqueness. It's not exactly the same, but I think it's related.

Preventing dependent property validation when the parent property does not exist

I am new to JSON schemas. I have a property (property1) that is dependent on another property (property2), which in turn is dependent on a third property (property3). I am trying to figure out how to prevent the schema from validating property1 if property2 doesn't exist. I am using the Python jsonschema module for validating.
I have a simple schema with three properties: species, otherDescription, and otherDescriptionDetail. The rules I'm trying to enforce are:
1) if species = "Human", otherDescription is required.
2) if species = "Human" and otherDescription != "None", otherDescriptionDetail is required.
3) if species != "Human", neither of the other two fields is required.
My test JSON correctly fails validation if species is "Human" and otherDescription doesn't exist, but it also reports that otherDescriptionDetail is a required property even though at this point it shouldn't be because there is no otherDescription value to compare it against. Is it possible to implement this logic with a JSON schema?
This is my schema:
"$schema": "http://json-schema.org/draft-07/schema#",
"$id":"http://example.com/test_schema.json",
"title": "annotations",
"description": "Validates file annotations",
"type": "object",
"properties": {
"species": {
"description": "Type of species",
"anyOf": [
{
"const": "Human",
"description": "Homo sapiens"
},
{
"const": "Neanderthal",
"description": "Cave man"
}
]
},
"otherDescription": {
"type": "string"
},
"otherDescriptionDetail": {
"type": "string"
}
},
"required": [
"species"
],
"allOf": [
{
"if": {
"properties": {
"species": {
"const": "Human"
}
}
},
"then": {
"required": ["otherDescription"]
}
},
{
"if": {
"allOf": [
{
"properties": {
"species": {
"const": "Human"
},
"otherDescription": {
"not": {"const": "None"}
}
}
}
]
},
"then": {
"required": ["otherDescriptionDetail"]
}
}
]
}
My test JSON is:
{
"species": "Human"
}
The output that I want:
0: 'otherDescription' is a required property
The output that I am getting:
0: 'otherDescription' is a required property
1: 'otherDescriptionDetail' is a required property
Any help would be greatly appreciated.
You need to defined otherDescription as a required property insilde allOf. Otherwise allOf block will pass even if otherDescription not available.
"if": {
"allOf": [
{
"properties": {
"species": {
"const": "Human"
},
"otherDescription": {
"not": {"const": "None"}
}
},
"required": ["otherDescription"]
}
]
},
"then": {
"required": ["otherDescriptionDetail"]
}

Fix wrong JSON values with Ajv

I am trying to fix property values when they have different value than specified.
JSON:
{
"stone": "bri"
}
I want to fix the stone property to have "brick" if it is not already "brick".
Schema:
{
"required": [
"stone"
],
"properties": {
"stone": {
"type": "string",
"if": {
"not": {
"constant": "brick"
}
},
"then": { "default": "brick" }
}
}
}
Using v4.11.4:
var ajv = new Ajv({ useDefaults: true, v5: true });
require('ajv-keywords')(ajv);
Is there any other keyword that I can use to basically change the value to the good one, or to an other value by reference?
Solved with a custom keyword:
ajv.addKeyword('modify_current', {
modifying: true,
validate: function (schema_parameter_value, validated_parameter_value, validation_schema_object, current_data_path, validated_parameter_object, validated_parameter) {
validated_parameter_object[validated_parameter] = schema_parameter_value;
return true;
},
errors: false
});
And in the schema:
"then": { "modify_current": "brick" }