Content of an property dependent on another property value - jsonschema

I have the following schema, which "works", but does not enforce all the rules required of it.
I get JSON with a series of questions that have a templateType and data properties. There are different templates for each type, and the type must fit the template (or the client doesn't know how to layout the data).
The schema validates the templateType as an enum, and that the data fits one of the templates, but there's no correlation between the type and data structure (e.g. I could get templateType yesNo and data structure for multiSelect).
I'd like it to validate that the templateType matches the data structure. I can't change the format of the generated JSON, only the schema that validates it. None of the questions I've looked at seem to provide a solution.
For help, the schema can be pasted into the editor at http://jeremydorn.com/json-editor/, which generates a form from the schema and JSON data based on selections and data entered into the form.
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"properties": {
"templateType": {
"type": "string",
"enum": ["yesNo","multiSelect"]
},
"data": {
"oneOf": [
{"$ref": "#/definitions/yesNo"},
{"$ref": "#/definitions/multiSelect"}
]
}
}
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}

Have you considered using if, then, else keywords in your schema? They're part of JSON Schema draft-07
It would look like this:
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"properties": {
"templateType": {
"type": "string",
"enum": ["yesNo","multiSelect"]
},
"data": {
"if": { "properties": { "templateType": { "pattern": "^yesNo$" } } },
"then": { "$ref": "#/definitions/yesNo" },
"else": { "$ref": "#/definitions/multiSelect" }
}
}
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}
If if/then/else isn't supported by your validator, an alternative could be:
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"anyOf": [
{
"properties": {
"templateType": { "type": "string", "pattern": "yesNo" },
"data": { "$ref": "#/definitions/yesNo" }
}
},
{
"properties": {
"templateType": { "type": "string", "pattern": "multiSelect" },
"data": { "$ref": "#/definitions/multiSelect" }
}
}
]
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}

Related

json schema validation: a string field required if another array field contains specific value

cant build validation for simple case:
if sources field contains "OTHER" in values then "sourceOtherDescription" must be required.
Shall pass validation
{
"sources": ["RENTS"]
}
{
"sources": ["RENTS", "OTHER"],
"sourceOtherDescription": "other income"
}
This should not pass validation since sources contains "OTHER"
{
"sources": ["RENTS", "OTHER"]
}
The schema that I was able to produce. Does not really work
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "money-sources",
"title": "Money Sources",
"description": "Money Sources definitions",
"type": "object",
"required": ["sources"],
"properties": {
"sources": {
"type": "array",
"items": {
"type": "string",
"enum": [
"RENTS",
"MEMBER_FEES",
"PROFIT",
"SALES_SECURITIES",
"INTERNAL_GROUP_TRANSFERS",
"OTHER"
]
},
"uniqueItems": true
},
"sourceOtherDescription": { "type": "string", "minLength": 3}
},
"additionalProperties": false,
"oneOf": [
{
"properties": {
"sources": {
"type": "array",
"contains": {"const": "OTHER"}
}
},
"required": ["sourceOtherDescription"]
},
{
"properties": {
"sources": {
"type": "array",
"contains": {
"enum": [
"RENTS",
"MEMBER_FEES",
"PROFIT",
"SALES_SECURITIES"
]
}
}
}
}
, false
]
}
Using if-then it works for me this way:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "money-sources",
"title": "Money Sources",
"description": "Money Sources definitions",
"type": "object",
"required": [ "sources" ],
"properties": {
"sources": {
"type": "array",
"items": {
"type": "string",
"enum": [
"RENTS",
"MEMBER_FEES",
"PROFIT",
"SALES_SECURITIES",
"INTERNAL_GROUP_TRANSFERS",
"OTHER"
]
},
"uniqueItems": true
},
"sourceOtherDescription": {
"type": "string",
"minLength": 3
}
},
"additionalProperties": false,
"if": {
"properties": {
"sources": {
"type": "array",
"contains": {
"const": "OTHER"
}
}
}
},
"then": {
"required": [ "sourceOtherDescription" ]
}
}

Json Schema dependency on outer field

Given a json structure like this
{
"name": "John Doe",
"billing_address": "123 main st",
"payment_details":{"credit_card": 55555555}
}
I need to make 'billing_address' a required field if payment_details.credit_card exists.
I've started from this example that achieves so if both fields were at the same level of nesting
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" }
},
"required": ["name"],
"dependencies": {
"credit_card": {
"properties": {
"billing_address": { "type": "string" }
},
"required": ["billing_address"]
}
}
}
However, what is the syntax to require this field that is one level above the dependant field?
That's too complicated for 'dependencies', so we go back to an if/then/else clause for that. We place these keywords at the top level, where the required field needs to be. In pseudocode: "if there is a payment_details property present, and it has a credit_card property, then require billing_address."
{
...,
"if": {
"type": "object",
"required": [ "payment_details" ],
"properties": {
"payment_details": {
"type": "object",
"required": [ "credit_card" ]
}
},
"then": {
"type": "object",
"required": [ "billing_address" ]
}
}
Note that the "type" and "required" keywords are required here -- as data of different types (for example an array) will cause object-specific keywords like "properties" and "required" to always evaluate to true.
thanks Ether! this worked, but only after adding the credit card as a property, in the if section.
the full if statement looks like this
"if": {
"type": "object",
"required": ["payment_details"],
"properties": {
"payment_details": {
"type": "object",
"properties": {
"credit_card": {}
},
"required": ["credit_card"]
}}},
the full json schema looks like this
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"billing_address": {
"type": "string"
},
"payment_details": {
"type": "object",
"properties": {
"credit_card": {
"type": "integer"
}
}
}
}
,
"if": {
"type": "object",
"required": ["payment_details"],
"properties": {
"payment_details": {
"type": "object",
"properties": {
"credit_card": {}
},
"required": ["credit_card"]
}}},
"then": {
"type": "object",
"required": [ "billing_address" ]
}
}

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!

JSON schema for object with either A and B or C required properties

I have two possible JSON objects for one request:
{
"from": "string",
"to": "string",
"text": "string"
}
or
{
"number": "integer",
"text": "string"
}
In both cases "text" property is optional. Other properties are required (either "number, or both "from" and "to").
What will be the correct JSON schema to validate this?
Here is another solution that I think is a bit more clear. The dependencies clause ensures that "from" and "to" always come as a pair. Then the oneOf clause can be really simple and avoid the not-required boilerplate.
{
"type": "object",
"properties": {
"from": { "type": "string" },
"to": { "type": "string" },
"number": { "type": "integer" },
"text": { "type": "string" }
},
"dependencies": {
"from": ["to"],
"to": ["from"]
},
"oneOf": [
{ "required": ["from"] },
{ "required": ["number"] }
]
}
Finally managed to build the correct scheme.
{
"definitions": {
"interval": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": ["from", "to"],
"not": {
"required": ["number"]
}
},
"top": {
"type": "object",
"properties": {
"number": {
"type": "integer"
},
"text": {
"type": "string"
}
},
"required": ["number"],
"allOf": [
{
"not": {
"required": ["from"]
}
},
{
"not": {
"required": ["to"]
}
}
]
}
},
"type": "object",
"oneOf": [
{"$ref": "#/definitions/interval"},
{"$ref": "#/definitions/top"}
]
}

JSONSchema v4 - Error when resolving schema reference - Definitions and References

When trying to validate the following schema using http://www.jsonschemavalidator.net/,
{
"id": "http://some.site.somewhere/entry-schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "schema for the FormularSpecification",
"definitions": {
"elementId": {
"id": "http://jsonschema.net/elementId",
"type": "string"
},
"mappingKey": {
"id": "http://jsonschema.net/mappingKey",
"type": "string"
},
"elementType": {
"id": "http://jsonschema.net/elementType",
"type": "string"
},
"length": {
"id": "http://jsonschema.net/length",
"type": "integer"
},
"label": {
"id": "http://jsonschema.net/label",
"type": "string"
},
"content": {
"id": "http://jsonschema.net/content",
"type": "string"
},
"placeholder": {
"id": "http://jsonschema.net/placeholder",
"type": "string"
},
"date": {
"id": "http://jsonschema.net/date",
"type": "string"
},
"option": {
"id": "http://jsonschema.net/option",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"label": { "$ref": "#/definitions/label" }
},
"required": ["elementId", "label"]
},
"options": {
"id": "http://jsonschema.net/options",
"type": "array",
"items": { "$ref": "#/definitions/option" },
"minItems": 1,
"uniqueItems": true
},
"textfield": {
"id": "http://jsonschema.net/textfield",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"length": { "$ref": "#/definitions/length" },
"label": { "$ref": "#/definitions/label" },
"placeholder": { "$ref": "#/definitions/placeholder" },
"textfieldType": {
"enum": [ "text", "ext4", "btrfs" ]
}
},
"required": ["elementId", "length", "label", "placeholder", "textfieldType"]
},
"checkbox": {
"id": "http://jsonschema.net/checkbox",
"type": "object",
"properties": {
"label": { "$ref": "#/definitions/label" }
},
"required": ["label"]
},
"radio": {
"id": "http://jsonschema.net/radio",
"type": "object",
"properties": {
"label": { "$ref": "#/definitions/label" },
"options": { "$ref": "#/definitions/options" }
},
"required": ["label", "options"]
},
"dropdown": {
"id": "http://jsonschema.net/dropdown",
"type": "object",
"properties": {
"label": { "$ref": "#/definitions/label" },
"options": { "$ref": "#/definitions/options" }
},
"required": ["label", "options"]
},
"validator": {
"id": "http://jsonschema.net/validator",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" }
}
},
"validators": {
"id": "http://jsonschema.net/validators",
"type": "array",
"items": { "$ref": "#/definitions/validator" }
},
"interactiveDetails": {
"type": "object",
"oneOf": [
{ "textfield": { "$ref": "#/definitions/textfield" } },
{ "checkbox": { "$ref": "#/definitions/checkbox" } },
{ "radio": { "$ref": "#/definitions/radio" } },
{ "dropdown": { "$ref": "#/definitions/dropdown" } },
{ "date": { "$ref": "#/definitions/date" } }
]
},
"interactive": {
"id": "http://jsonschema.net/interactive",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"elementType": { "$ref": "#/definitions/elementType" },
"mappingKey": { "$ref": "#/definitions/mappingKey" },
"validators": { "$ref": "#/definitions/validators" },
"interactiveDetails" : { "$ref": "#/definitions/interactiveDetails" }
},
"required": ["elementId", "elementType", "mappingKey", "validators"]
},
"interactives": {
"id": "http://jsonschema.net/interactives",
"type": "array",
"items": { "$ref": "#/definitions/interactive" }
},
"description": {
"id": "http://jsonschema.net/description",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"elementType": { "$ref": "#/definitions/elementType" },
"content": { "$ref": "#/definitions/content" }
},
"required": ["elementId", "elementType", "content"]
},
"descriptions": {
"items": { "$ref": "#/definitions/description" }
},
"children": {
"items": {
"anyOf": [
{ "$ref": "#/definitions/group" },
{ "$ref": "#/definitions/question" }
]
},
"minItems": 1
},
"question": {
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"descriptions": { "$ref": "#/definitions/descriptions" },
"interactives": { "$ref": "#/definitions/interactives" }
},
"required": ["elementId", "descriptions", "interactives"]
},
"group": {
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"descriptions": { "$ref": "#/definitions/descriptions" },
"children": { "$ref": "#/definitions/children"}
},
"required": ["elementId", "descriptions", "children"]
}
},
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"description": { "$ref": "#/definitions/descriptions" },
"children": { "$ref": "#/definitions/children" }
},
"required": [
"elementId",
"descriptions",
"children"
]
}
I'm getting the following error:
Error when resolving schema reference '#/definitions/elementId'. Path 'definitions.description.properties.elementId', line 135, position 30.
I cannot figure out what the problem is. I scanned the documentation several times and had a look at tutorials, but I do not have any clue.
The semantics of the id keyword are are a bit confusing. I'm not sure I completely understand it myself. In general it is almost never a good idea to include id anywhere other than the root of your schema.
The "id" keyword (or "id", for short) is used to alter the resolution scope. When an id is encountered, an implementation MUST resolve this id against the most immediate parent scope. The resolved URI will be the new resolution scope for this subschema and all its children, until another id is encountered.
http://json-schema.org/latest/json-schema-core.html#anchor27
Consider the following excerpt from you schema. Because you include the id keyword, your "elementId" and "label" $refs don't resolve against the root of the document as you expect, they resolve from the nearest parent schema id.
"option": {
"id": "http://jsonschema.net/option",
"type": "object",
"properties": {
"elementId": { "$ref": "#/definitions/elementId" },
"label": { "$ref": "#/definitions/label" }
},
"required": ["elementId", "label"],
"definitions": { ... } <-- your $refs expect values here
}
},
I have seen that in some circles, people write schemas with ids for every subschema. I'm not sure what benefit they think they are getting out of doing that, but I suspect that they think of id as just a label and don't understand how it alters resolution scope.
If you do have good reason to use ids everywhere and want to leave them in, you can always explicitly reference the root id when you have a conflict.
{ "$ref": "http://some.site.somewhere/entry-schema#definitions/elementId" }