JSON Schema - conditional validation - jsonschema

I have the following Schema. I've implemented it as best I can, but it's still not working quite as I want it to.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Ordering pizza",
"propertyNames": {
"enum": [
"q-who-did-you-order-from",
"q-did-they-accept-your-order",
"q-how-much-was-the-bill",
"q-why-didnt-they-accept"
]
},
"properties": {
"q-who-did-you-order-from": {
"type": "string",
"title": "Who have you ordered pizza from?",
"maxLength": 50,
"errorMessages": {
"required": "Enter who you ordered from",
"maxLength":
"Who you ordered from must be 50 characters or less"
}
},
"q-did-they-accept-your-order": {
"title": "Have they accepted your order?",
"type": "boolean",
"errorMessages": {
"required":
"Select yes if they have accepted your order"
}
},
"q-how-much-was-the-bill": {
"type": "string",
"title": "How much was the bill?",
"maxLength": 50,
"errorMessages": {
"required": "Enter an amount",
"maxLength": "Amount must be 50 characters or less"
}
},
"q-why-didnt-they-accept": {
"type": "string",
"title": "Why wasnt your order accepted?",
"description":
"If you do not know you can say so.",
"maxLength": 50,
"errorMessages": {
"required": "Enter a reason",
"maxLength": "Reason must be 50 characters or less"
}
}
},
"required": ["q-who-did-you-order-from", "q-did-they-accept-your-order"],
"allOf": [
{
"$ref": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required"
},
{
"$ref": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required"
}
],
"definitions": {
"if-false-then-q-why-didnt-they-accept-is-required": {
"if": {
"properties": {
"q-did-they-accept-your-order": {
"const": false
}
}
},
"then": {
"required": ["q-why-didnt-they-accept"],
"propertyNames": {
"enum": [
"q-who-did-you-order-from",
"q-did-they-accept-your-order",
"q-why-didnt-they-accept"
]
}
}
},
"if-true-then-q-how-much-was-the-bill-is-required": {
"if": {
"properties": {
"q-did-they-accept-your-order": {
"const": true
}
}
},
"then": {
"required": ["q-how-much-was-the-bill"],
"propertyNames": {
"enum": [
"q-who-did-you-order-from",
"q-did-they-accept-your-order",
"q-how-much-was-the-bill"
]
}
}
}
}
}
The expectation is that the user will enter a value for q-who-did-you-order-from and q-did-they-accept-your-order, then only one of the remaining two questions based on their answer for q-did-they-accept-your-order.
So the following inputs should validate:
{
"q-did-you-order-from": "Pizza hut",
"q-did-they-accept-your-order": "true",
"q-how-much-was-the-bill": "20"
}
{
"q-did-you-order-from": "Pizza hut",
"q-did-they-accept-your-order": "false",
"q-why-didn't-they-accept": "Incorrect card details"
}
Similarly, I would expect the following inputs to fail validation and throw a 'required' error for the field containing a blank string. The first should throw an error because q-why-didn't-they-accept is empty:
{
"q-did-you-order-from": "Pizza hut",
"q-did-they-accept-your-order": "false",
"q-why-didn't-they-accept": ""
}
And this one should throw an error because q-how-much-was-the-bill is empty.
{
"q-did-you-order-from": "Pizza hut",
"q-did-they-accept-your-order": "true",
"q-how-much-was-the-bill": ""
}
And it does! This works as expected. However, we found a bug that arises from the user not entering an answer to q-did-they-accept-your-order. The answers to these questions are POSTed via the browser on form submission. In the browser, the boolean question is presented as yes/no radio buttons. As a result, when the user does not check either radio, but does try to submit the form, the answer for the radios is omitted entirely. The data object sent looks like this:
{
"q-did-you-order-from": "Pizza hut",
"q-how-much-was-the-bill": "",
"q-why-didn't-they-accept": "",
}
My EXPECTED result here:
AJV throws only one 'required' error for q-did-they-accept-your-order. It shouldn't throw a 'required' error for anything else as both q-how-much-was-the-bill and q-why-didn't-they-accept aren't required unless the related value for q-did-they-accept-your-order is selected.
My ACTUAL result:
AJV Throws an error for all three empty inputs.
So my question is, how do I get AJV to validate this schema and for ONLY q-did-they-accept-your-order to throw a required error when the question is not answered.
EDIT:
The output from AJV is as follows:
[
{
"keyword": "required",
"dataPath": "",
"schemaPath": "#/required",
"params": {
"missingProperty": "q-did-they-accept-your-order"
},
"message": "should have required property 'q-did-they-accept-your-order'"
},
{
"keyword": "required",
"dataPath": "",
"schemaPath": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required",
"params": {
"missingProperty": "q-why-didnt-they-accept"
},
"message": "should have required property 'q-why-didnt-they-accept'"
},
{
"keyword": "if",
"dataPath": "",
"schemaPath": "#/definitions/if-false-then-q-why-didnt-they-accept-is-required/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
},
{
"keyword": "required",
"dataPath": "",
"schemaPath": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required/then/required",
"params": {
"missingProperty": "q-how-much-was-the-bill"
},
"message": "should have required property 'q-how-much-was-the-bill'"
},
{
"keyword": "if",
"dataPath": "",
"schemaPath": "#/definitions/if-true-then-q-how-much-was-the-bill-is-required/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]

There's a part missing from your application of the if/then pattern. Let's use this simple schema as an example.
{
"if": {
"properties": {
"foo": { "const": true }
}
},
"then": {
"required": ["bar"]
}
}
If I validate {} against this schema, it will fail saying that the property "bar" is required. Because the /if schema doesn't require the "foo" property, {} is valid and therefore the /then schema applies. To fix this problem, you just need to make the "foo" property required.
{
"if": {
"properties": {
"foo": { "const": true }
},
"required": ["foo"]
},
"then": {
"required": ["bar"]
}
}
Now, {} is valid against the schema. the /then schema will only apply if there is a "foo" property and it's value is true.

Related

How to improve JSON Schema validation results when using "anyOf"

We want to validate an array of elements with a JSON Schema Validator (https://github.com/networknt/json-schema-validator if that makes any difference).
Unfortunately we get quite ambiguous error messages if the validation fails and I am wondering if there is a way to improve this.
To make it clearer, I have created a small sample:
Each element in the array has a property "pet_type" which we can be sure to be there all the time (yeah I know that is discussable, but for the sake of argument let's ignore that for now). Each element has then some other properties.
This is the json schema right now.
Envelope (Basically with an array of elements)
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "./Cat.json"
},
{
"$ref": "./Dog.json"
}
]
}
}
}
}
Cat (one of the elements in the array)
{
"title": "Cat",
"allOf": [
{
"$ref": "Pet.json"
},
{
"type": "object",
"properties": {
"hunts": {
"type": "boolean"
},
"age": {
"type": "integer"
},
"pet_type": {
"type": "string",
"enum": [
"Cat"
],
"pattern": "Cat"
}
},
"required": [
"pet_type",
"age"
]
}
]
}
Dog:
{
"title": "Dog",
"allOf": [
{
"$ref": "Pet.json"
},
{
"type": "object",
"properties": {
"bark": {
"type": "boolean"
},
"breed": {
"type": "string"
},
"pet_type": {
"type": "string",
"enum": [
"Dog"
],
"pattern": "Dog"
}
},
"required": [
"bark"
]
}
]
}
And the Pet.json, which specifies that "pet_type" is required on each of the elements.
{
"title": "Pet",
"type": "object",
"discriminator": {
"propertyName": "pet_type"
},
"properties": {
"pet_type": {
"type": "string",
"enum": [
"Cat",
"Dog"
]
}
},
"required": [
"pet_type"
]
}
The problem I have is that the error messages get very long, because the element is validated against all of the constraints, without narrowing it down a bit.
Let's say the JSON to validate looks like this:
{
"pets": [
{
"pet_type": "Cat",
"hunts": true
}
]
}
"pet_type" is set to "Cat", so for a human it is "clear" that it should only validate against the Cat JSON Schema and then show that "age" is missing.
What really happens is that there are 4 violations:
age is missing (from the Cat JSON Schema)
bark is missing (from the Dog JSON Schema)
pet_type is violating the "Dog" enum contstraint (from the Dog JSON Schema)
pet_type is violating the "Dog" regex pattern (from the Dog JSON Schema)
Note: I have added the enum/regex pattern in an act of trying to change the behavior, but it did not work.
I understand from a technical standpoint why it behaves like it does, I just want to know if it is possible to tell the validator somehow to first narrow down the validation based on pet_type and then keep validating?
What I tried to get it working:
I have tried to set the pet_type to string with an enum "Cat" and "Dog" and added one of those values to the Cat/Dog JSON Schema respectively in an attempt to make it clear which event has which pet_type.
I have tried to remove the Pet.json Schema completely, but that did not change anything.
I have tried to use if/then, but somehow this gets rid of all validation errors:
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"if": {
"properties": {
"pet_type": {
"const": "Cat"
}
}
},
"then": {
"$ref": "./Cat.json"
}
},
{
"if": {
"properties": {
"pet_type": {
"const": "Dog"
}
}
},
"then": {
"$ref": "./Dog.json"
}
}
]
}
}
}
}
My sample I tried to validate gives no errors anymore:
{
"pets": [
{
"pet_type": "Cat",
"hunts": true
}
]
}
Turns out the solution with if/then was correct, and the json-schema-validator actually detects the problems, but has some code that should improve the error messages, but gets rid of the validation errors instead.
I will bring the problem to their attention.
For now I have found a workaround to use nested if/then/else statements:
{
"title": "Envelope",
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"if": {
"properties": {
"pet_type": {
"const": "Cat"
}
}
},
"then": {
"$ref": "./Cat.json"
},
"else": {
"if": {
"properties": {
"pet_type": {
"const": "Dog"
}
}
},
"then": {
"$ref": "./Dog.json"
},
"else": {
"properties": {
"pet_type": {
"type": "string",
"enum": [
"Cat",
"Dog"
]
}
}
}
}
}
}
},
"additionalProperties": false
}

Extra error message by JSON Schema validator when if-else is there

Here is my JSON Schema(hypothetical, as I cannot share my actual one) and the JSON. The if-then-else condition is like:
at a time only one (1) out of three (3) country can appear to define statistics.
Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"year",
"country",
"statistics"
],
"definitions": {
"statisticsUsa": {
"if": {
"properties": {
"type": {
"const": "statisticsUsa"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"type": {
"const": "statisticsUsa"
}
}
}
},
"statisticsFinland": {
"if": {
"properties": {
"type": {
"const": "statisticsFinland"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"type": {
"const": "statisticsFinland"
}
}
}
},
"statisticsGermany": {
"if": {
"properties": {
"type": {
"const": "statisticsGermany"
}
},
"required": [
"type", "literacy"
]
},
"then": {
"properties": {
"type": {
"const": "statisticsGermany"
},
"literacy": {
"type": "string"
}
}
}
}
},
"properties": {
"year": {
"type": "string"
},
"country": {
"type": "string",
"enum": [
"USA",
"FINLAND",
"GERMANY"
]
},
"statistics": {
"type": "array",
"allOf": [{
"if": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsUsa"
]
}
}
}
},
"then": {
"not": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsFinland",
"statisticsGermany"
]
}
}
}
}
},"errorMessage" :"Only USA applicable at a time for statistics"
},
{
"if": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsFinland"
]
}
}
}
},
"then": {
"not": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsUsa",
"statisticsGermany"
]
}
}
}
}
},"errorMessage" :"Only Finland applicable at a time for statistics"
},
{
"if": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsGermany"
]
}
}
}
},
"then": {
"not": {
"contains": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"statisticsUsa",
"statisticsFinland"
]
}
}
}
}
},"errorMessage" :"Only Germany applicable at a time for statistics"
}
],
"minItems": 1,
"items": {
"anyOf": [{
"$ref": "#/definitions/statisticsUsa"
},
{
"$ref": "#/definitions/statisticsFinland"
},
{
"$ref": "#/definitions/statisticsGermany"
}
]
}
}
}
}
Json 1
const Ajv = require('ajv');
const ajv = new Ajv({
allErrors: true,
jsonPointers: true
});
const schema1 = require("./sof.json");
require('ajv-errors')(ajv /*, {singleError: true} */);
const data = {
year: "2000",
country: "USA",
statistics: [
{"type":"statisticsUsa"},
{"type":"statisticsFinland"}
]
}
let valid = ajv.validate(schema1, data); //schema, data
if (!valid) {
console.log(`${JSON.stringify(ajv.errors,null,2)}`);
}
Here, I can see 2 errors as part of the array, when I have 2 countries. My question is, being 'finland' cannot have with 'USA', the error for "Only USA applicable at a time for statistics" is NOT acceptbale, as it satisfies the condition. But, The error for Findland is acceptable.
[
{
"keyword": "errorMessage",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/errorMessage",
"params": {
"errors": [
{
"keyword": "not",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/then/not",
"params": {},
"message": "should NOT be valid"
},
{
"keyword": "if",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]
},
"message": "Only USA applicable at a time for statistics"
},
{
"keyword": "errorMessage",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/errorMessage",
"params": {
"errors": [
{
"keyword": "not",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/then/not",
"params": {},
"message": "should NOT be valid"
},
{
"keyword": "if",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]
},
"message": "Only Finland applicable at a time for statistics"
}
]
Also, if I add 'Germany' in JSON as,
{
year: "2000",
country: "USA",
statistics: [
{"type":"statisticsUsa"},
{"type":"statisticsFinland"},
{"type":"statisticsGermany"}
]
}
it throws 3 errors as part of the array (See below). Why?
Technically, the schema validator should stop at 2nd item and should ignore the 'Germany' in the error array as it finds 'Findland' entry is violating the if-else conditions.
Also, does schema-validator takes each item from 'statistics' array (from the JSON) and checks the conditions within 'allOf' for every run and that's why 3 error entries in the array. Is my understanding correct. (If you see the first error array above, it has 2 only entries)
[
{
"keyword": "errorMessage",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/errorMessage",
"params": {
"errors": [
{
"keyword": "not",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/then/not",
"params": {},
"message": "should NOT be valid"
},
{
"keyword": "if",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/0/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]
},
"message": "Only USA applicable at a time for statistics"
},
{
"keyword": "errorMessage",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/errorMessage",
"params": {
"errors": [
{
"keyword": "not",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/then/not",
"params": {},
"message": "should NOT be valid"
},
{
"keyword": "if",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/1/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]
},
"message": "Only Finland applicable at a time for statistics"
},
{
"keyword": "errorMessage",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/2/errorMessage",
"params": {
"errors": [
{
"keyword": "not",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/2/then/not",
"params": {},
"message": "should NOT be valid"
},
{
"keyword": "if",
"dataPath": "/statistics",
"schemaPath": "#/properties/statistics/allOf/2/if",
"params": {
"failingKeyword": "then"
},
"message": "should match \"then\" schema"
}
]
},
"message": "Only Germany applicable at a time for statistics"
}
]
You already deduced it yourself: the allOf contains applies all subschemas and checks against all array items.
If you want only one error message, then you should decide the priority of countries and only check in subsequent allOf parts for the remaining countries and not all of them each time. E.g.
If the array contains a “USA” entry, neither “GERMANY” nor “FINLAND” should be present (as you already have it)
If the array contains a “GERMANY” entry, no “FINLAND” entry should be present (do not check for “USA” again)
If the array contains a “FINLAND” entry, the possible scenarios are already covered by the two checks above – no need for any additional check here.
That way, you never get multiple of these errors at the same time.

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

can I do a patternProperties with $ref validation

I am trying to match a patternProperties with a schema, like, here is the jsonschema text:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"fabric_id": {
"enum": [
"ADMIN",
"COPPER",
"NETWORK",
"STORAGE",
"STORAGE2",
"TENANT"
]
},
"fabrics": {
"additionalProperties": false,
"patternProperties": {
"[A-Z0-9-]*": {
"additionalProperties": false,
"properties": {
"description": {
"type": "string"
},
"fabric_id": {
"$ref": "#/definitions/fabric_id",
"type": "string"
}
},
"required": [
"description",
"fabric_id"
],
"type": "object"
}
},
"type": "object"
}
},
"description": "fabrics spec",
"properties": {
"fabrics": {
"$ref": "#/definitions/fabrics"
}
},
"required": [
"fabrics"
],
"title": "network fabric",
"type": "object"
}
and here is my input json file:
{
"fabrics": {
"ADMIN": {
"description": "Primary bonded pair on the bigswitches.",
"fabric_id": "ADMIN"
},
"COPPER": {
"description": "Primary IPMI fabric on the tor switches.",
"fabric_id": "COPPER"
}
}
}
I can't figure out how to validate the patternProperty against the fabric_id enum? The pattern object has fabric_id in it, and that is able to reference the fabric_id enum in the definitions section. I'd like to have that same $ref for the "[A-z0-9-]*" pattern, but I just can't make it work. Is this possible?
This schema does is the best you can do. The only thing it can't do is constrain the property name to match the value of "fabric_id". Unfortunately, this is not possible with JSON Schema.
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"properties": {
"fabrics": { "$ref": "#/definitions/fabrics" }
},
"required": ["fabrics"],
"definitions": {
"fabric_id": {
"enum": ["ADMIN", "COPPER", "NETWORK"]
},
"fabrics": {
"type": "object",
"propertyNames": { "$ref": "#/definitions/fabric_id" },
"patternProperties": {
".*": {
"type": "object",
"properties": {
"description": { "type": "string" },
"fabric_id": { "$ref": "#/definitions/fabric_id" }
},
"required": ["description", "fabric_id"]
}
}
}
}
}
Sadly, I don't believe this is possible with draft-4 of JSON Schema.
If you can upgrade to 6 or 7 (+), you can make this possible.
propertyNames: https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.5.8
If the instance is an object, this keyword validates if every property name in the instance validates against the provided schema. Note the property name that the schema is testing will always be a string.
An example of how this can be used can be found at https://github.com/json-schema-org/json-schema-org.github.io/issues/77
...
"fooProperties": {
"propertyNames": {
"$comment": "Need to anyOf these or else the enum and pattern conflict",
"anyOf": [
{"enum": ["foo1", "foo2"]},
{"pattern": "foo[A-Z][a-z0-9]*"}
]
}
},
...
Sorry I don't have time to update your schema to follow this, but hopefully I sufficiently explained this for you to adapt it.
if you're unable to migrate beyond draft-4... well you'll have to do that validation aspect manually outside of JSON Schema.

Json property structure dependant of another property

I've been working on a json schema to validate the answers from one of my webservices.
The answer is splitted in two properties: data and status. If status.code is set to 0, then data will have to respect a specific schema. Else, if status.code is set to -1, data won't be read, so I don't want to check if it respects the schema.
Here is the schema :
{
"$schema": "http://json-schema.org/schema#",
"id": "file://registration.json",
"type": "object",
"properties": {
"status": {
"$ref": "#/definitions/classes/status"
}
},
"anyOf": [
{
"$ref": "#/definitions/conditions/status-is-ok"
},
{
"$ref": "#/definitions/conditions/status-is-nok"
}
],
"definitions": {
"classes": {
"status": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
},
"data": {
"type": "object",
"properties": {
"propertyA": {
"type": "#/definitions/classes/metadatauser"
},
"propertyB": {
"type": "#/definitions/classes/membreinfo"
}
},
"required": ["propertyA", "propertyB"]
}
},
"conditions": {
"status-is-ok": {
"status": {
"properties": {
"code": 0
}
},
"data": {
"$ref": "#/definitions/classes/data"
}
},
"status-is-nok": {
"status": {
"properties": {
"code": -1
}
},
"data": {
"type": "object"
}
}
}
}
}
And here's an example of what should not be validated:
{
"data": {},
"status": {
"code": 0,
"message": "OK"
}
}
At the moment, this portion of code passes, and I don't know why.
You've got a few things wrong here, so I'll try to explain all of them. You were on the right track!
"properties": {
"code": 0
}
The value of "properties" MUST be an object. Each value of this object
MUST be a valid JSON Schema.
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.4
You can't put the value you expect as the value for a property key.
You CAN however use the [const]1 keyword to achive a specific value validation.
"$ref": "#/definitions/conditions/status-is-ok"
...
"conditions": {
"status-is-ok": {
"status": {
"properties": {
[The definitions] keyword's value MUST be an object. Each member value of this
object MUST be a valid JSON Schema.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-9
This means that you need to treat each value of each key in a defintions as a JSON Schema. If you had a JSON Schema where you did not nest "status" inside a properties object, no validation would take place. The same is true for "data".
(Strictly, according to the definitions section of the spec, you MUST NOT nest schemas deeply in the definitions object, but this seems to be supported by some implementations anyway, and resolves using the correct resolution rules. Prefixing may be better.)
The complete fixed schema is as follows.
{
"$schema": "http://json-schema.org/schema#",
"id": "file://registration.json",
"type": "object",
"properties": {
"status": {
"$ref": "#/definitions/classes/status"
}
},
"anyOf": [
{
"$ref": "#/definitions/conditions/status-is-ok"
},
{
"$ref": "#/definitions/conditions/status-is-nok"
}
],
"definitions": {
"classes": {
"status": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
},
"data": {
"type": "object",
"properties": {
},
"required": [
"propertyA",
"propertyB"
]
}
},
"conditions": {
"status-is-ok": {
"properties": {
"status": {
"properties": {
"code": {
"const": 0
}
}
},
"data": {
"$ref": "#/definitions/classes/data"
},
},
"additionalProperties": false
},
"status-is-nok": {
"properties": {
"status": {
"properties": {
"code": {
"const": -1
}
}
},
"data": {
"type": "object"
},
},
"additionalProperties": false
}
}
}
}
Please do let me know if any of this doesn't make sense.
Feel free to join the JSON Schema slack server should you want to discuss any aspects further! Happy to also comment here.