JSON Schema - combining allOf and anyOf to make a flexible schema - json

In plain English, I want a flexible schema that will allow an object called "message" to contain a couple of string props, and then a 3rd prop that can be either a plain string or another object. So, if it's defined thusly, is this achieving the goal or is there a validation gotcha that I am missing?
"message": {
"type": "object",
"properties": {
"another": {
"$ref": "another.schema.json"
},
"#type": {
"type": "string",
"enum": [
"One",
"Two",
"Three"
]
}
},
"allOf": [
{
"$ref": "#message"
},
{
"anyOf": [
{
"plainolstring": {
"type": "string"
}
},
{
"obj": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
}
]
}
]
}
}
And that would validate against either
"message": {
"another": "blah",
"#type": "foo",
"plainolstring": "sdfSR345w34"
}
or
"message": {
"another": "blah",
"#type": "foo",
"obj": {
"id":"sdfSR345w34",
"type": "guid"
}
}
Validating here against Schema v7 says that it is valid, but while it may be syntactically correct, would it achieve what I want?

Yes, anyOf will do what you want - except you have a syntax error by wrapping the subschemas with those properties "plainolstring" and "obj". The correct form would be:
{
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
]
}
Moreover, you don't even need the anyOf -- you can simplify that subschema to:
{
"type": ["string", "object"],
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
The properties keyword itself does not enforce that the type must be an object -- you need to use type for that. properties just says "if this is an object, this is the definition of those properties".

Related

JSON Schema if-else condition complex scenario

{
"policyHolder": {
"fullName": "A"
},
"traveller": [
{
"fullName": "B",
"relationship": "Spouse"
},
{
"fullName": "A",
"relationship": "My Self"
}
]
}
In above json, I want to validate that
if "relationship" = "My Self" then fullName must match the fullName in policyHolder
A field relationship must exist in traveller array, else json is invalid
I have tried to create a json schema with if-else, allOf, etc. but nothing works which can do these validations but not able to.
Please help!!
Schema:
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"properties": {
"policyHolder": {
"$id": "#/properties/policyHolder",
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": {
"$id": "#/properties/policyHolder/properties/fullName",
"type": "string",
}
}
},
"traveller": {
"$id": "#/properties/traveller",
"type": "array",
"minItems": 1,
"items": {
"$id": "#/properties/traveller/items",
"type": "object",
"properties": {
"fullName": {
"$ref": "#/properties/policyHolder/properties/fullName"
},
"relationship": {
"$id": "#/properties/traveller/items/properties/relationship",
"type": "string",
}
},
"required": [
"fullName",
"relationship"
],
}
}
}
}```
It's your first requirement that you're going to have the most trouble with. JSON Schema doesn't support validation of data against data elsewhere in the instance. It's a highly discussed topic, but nothing has been adopted yet. I suggest you verify this with a little code.
For the second, I would suggest you extract some of your subschemas into definitions rather than trying to muck about with IDs. IDs are typically more beneficial if you're referencing them from other documents or if you use short (like single-word) IDs. Defining the ID as its location in the document is redundant; most processors will handle this automatically.
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"definitions": {
"person": {
"type": "object"
"properties": {
"fullName": {"type": "string"}
},
"required": ["fullName"]
},
"relationship": { "enum": [ ... ] } // list possible relationships
},
"properties": {
"policyHolder": { "$ref": "#/definitions/person" },
"traveller": {
"type": "array",
"minItems": 1,
"items": {
"allOf": [
{ "$ref": "#/definitions/person" },
{
"properties": {
"relationship": { "$ref": "#/definitions/relationship" }
},
"required": ["relationship"]
}
]
}
}
}
}
(I extracted the relationship into its own enum definition, but this is really optional. You can leave it inline, or even an unrestricted string if you don't have a defined set of relationships.)
This can't currently be done with JSON Schema. All JSON Schema keywords can only operate on one value at a time. There's a proposal for adding a $data keyword that would enable doing this kind of validation, but I don't think it's likely to be adopted. $data would work like $ref except it references the JSON being validated rather than referencing the schema.
Here's what how you would solve your problem with $data.
{
"type": "object",
"properties": {
"policyHolder": {
"type": "object",
"properties": {
"fullName": { "type": "string" }
}
},
"traveler": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fullName": { "type": "string" },
"relationship": { "type": "string" }
},
"if": {
"properties": {
"relationship": { "const": "My Self" }
}
},
"then": {
"properties": {
"fullName": { "const": { "$data": "#/policyHolder/fullName" } }
}
}
}
}
}
}
Without $data, you will have to do this validation in code or change your data structure so that it isn't necessary.

JSON Schema Nested If Then

I cannot seem to find a working way of applying multiple if/then logic on an enum.
anyOf doesnt apply the conditional logic, but instead it says if any of them match then thats good.
allOf again doesnt apply the conditional logic but tests a superset of the properties/required fields.
Here is a JSON Schema example:
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"type"
],
"properties": {
"type": {
"$id": "#/properties/type",
"enum": [
"a",
"b",
"c"
],
"title": "The Type"
},
"options": {
"$id": "#/properties/options",
"type": "object",
"title": "The Options Schema",
"oneOf": [
{
"if": { "properties": { "type": { "const": "a" } }
},
"then": {
"required": [ "option1" ],
"properties": {
"option1": {
"$id": "#/properties/options/properties/option1",
"type": "boolean",
"title": "The option1 Schema"
}
}
}
},
{
"if": { "properties": { "type": { "const": "b" } }
},
"then": {
"required": [ "option2" ],
"properties": {
"option2": {
"$id": "#/properties/options/properties/option2",
"type": "boolean",
"title": "The option2 Schema"
}
}
}
},
{
"if": { "properties": { "type": { "const": "c" } }
},
"then": {
"required": [],
"properties": {}
}
}
]
}
}
}
If you validate against this JSON:
{
"type": "a",
"options": {
"option1": true
}
}
It fails because option2 is required.
If you change it to anyOf then it succeeds, but if you change the JSON to be invalid:
{
"type": "a",
"options": {
"option2": false
}
}
It still succeeds.
I havent managed to get nested if/then/else/if/then/else working either.
How can i perform a check where I have set of properties for each type and you cannot intermingle them? Is this actually possible, or should I just change my design.
First, you can test your schemas here. There are several of these sites across the internet.
Second, the if/then/else construct was introduced to replace a oneOf for these kind of enum scenarios, not be combined with it.
This subschema
"if": { "properties": { "type": { "const": "a" } } },
"then": {
"required": [ "option1" ],
"properties": {
"option1": {
"$id": "#/properties/options/properties/option1",
"type": "boolean",
"title": "The option1 Schema"
}
}
}
doesn't actually fail when type is not a. It merely says that if type=a, apply the then subschema. It doesn't say anything about what to validate if type is not a, so it passes. If you add an else:false to this, it'll be more in line with what you're thinking, but I encourage you to think about it differently.
Use oneOf or if/then/else, but not both. I suggest changing your subschemas to use this format:
{
"properties": {
"type": { "const": "a" },
"option1": {
"$id": "#/properties/options/properties/option1",
"type": "boolean",
"title": "The option1 Schema"
}
},
"required": [ "option1" ],
}
This asserts that option1 is required and must be a boolean, and that type=a. If type is not a, this schema fails, which is what you want.
This answer describes what you need to do in a bit more detail.

How to describe this validation requirement in jsonschema?

I want to validate my API json response like this:
{
"code": 0,
"results": [
{"type":1, "abc": 123},
{"type":2, "def": 456}
]
}
I want to validate the objects within results to have a "abc" field when its type is 1, and "def" field when its type is 2. The results may contain arbitrary number of type1 and type2 objects.
Can I specify this in jsonschema? Or must I use a generic validator for elements in results and do validation myself?
You can do this using the anyOf keyword.
An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value.
http://json-schema.org/latest/json-schema-validation.html#anchor85
You need to define both types of items and then use anyOf To describe the array items for "results".
{
"type": "object",
"properties": {
"code": { "type": "integer" },
"results": {
"type": "array",
"items": { "$ref": "#/definitions/resultItems" }
}
},
"definitions": {
"resultItems": {
"type": "object",
"anyOf": [
{ "$ref": "#/definitions/type1" },
{ "$ref": "#/definitions/type2" }
]
},
"type1": {
"properties": {
"type": { "enum": [1] },
"abc": { "type": "integer" }
},
"required": ["abc"]
},
"type2": {
"properties": {
"type": { "enum": [2] },
"def": { "type": "integer" }
},
"required": ["def"]
}
}
}

Can maxItems/minItems be used with a $ref in a JSON schema

Given a JSON schema with the following in the definitions section:
"phoneNumber": {
"type": "object",
"properties": {
"countryCode": {
"type": "number"
},
"areaCode": {
"type": "number"
},
"number": {
"type": "number"
},
"extension": {
"type": "number"
},
"service": {
"type": "string",
"enum": ["Voice", "Fax", "Data"]
},
"class": {
"type": "string",
"enum": ["Switchboard", "Direct", "PA", "Mobile"]
}
}
}
If I want to include phoneNumber elsewhere using a $ref and want the JSON to validate if it contains multiple occurrences of phoneNumber, can I use maxItems/minItems:
"person": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"phoneNumber": {
"$ref": "#/definitions/phoneNumber"
//can I use maxItems/minItems here?
}
}
}
Can I use maxItems and minItems here, or would I have to do something like this below for it to validate:
"phoneNumber": {
"allOf": { "$ref": "#/definitions/phoneNumber" },
"maxItems": 4
}
$ref must stand alone. The option you identified using allOf is the best way to do it.
Any members other than "$ref" in a JSON Reference object SHALL be ignored.
https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03#section-3

Create JSON Schema value dependency

I have a JSON schema I want to change by a dependency of one of the values of the JSON structure. For example if {"foo":1} also include {"fooBar":"number"} in the schema, resulting in {"foo":"number", "fooBar":"number"} but if {"foo":2} instead include {"fooBar2":"bool", "fooBar3":"string"} resulting in {"foo":1, "fooBar2":"bool", "fooBar3":"string"}. Is this possible.
I know how to make the inclusion of a key change the schema (code example from here) but I cannot find any example on how this could be done using values. If this is even possible.
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependencies": {
"credit_card": ["billing_address"]
}
}
It can be done, but it's a little complicated. Below is the general pattern.
{
"type": "object",
"anyOf": [
{
"properties": {
"foo": { "enum": [1] },
"fooBar": { "type": "number" }
}
},
{
"properties": {
"foo": { "enum": [2] },
"fooBar2": { "type": "boolean" },
"fooBar3": { "type": "string" }
}
}
]
}