JSON Schema: require properties based on values of other properties
JSON Schema is a specification for defining schemas used to validate JSON objects. I recently had to figure out how to make sure certain properties are present, depending on a numeric value of another property.
If you want to experiment along, JSON Schema Validator is a good place to build up schemas and test them against inputs.
Use case: train tickets require a seat number
Let’s take a look at it from an imaginary example where a system books bus and train tickets. For simplicity, the JSON object contains two properties: ticket type and seat number.
If the ticket booked is a bus ticket (stored as value 1 in our system), there’s no need for seat number but if the ticket is a train ticket (stored as value 2 in our system), a seat number must be included.
Bus ticket looks like this:
{
"ticketType": 1
}
and a train ticket like this:
{
"ticketType": 2,
"seat": 42
}
Please note: In the examples below, I’m only interested in making sure the
seat
is included with train ticket but I
don’t do anything to make sure it is
not included with a bus ticket. In my case,
they were discarded in another step.
Add constraints to schema
To start with, here’s what our schema looks like (I’ve omitted metadata like id and schema):
{
"type": "object",
"properties": {
"ticketType": {
"type": "integer"
},
"seat": {
"type": "integer"
}
},
"required": ["ticketType"]
}
Now we want to add constraint. that if
ticketType == 2
,
seat
is required.
To do that, we have a couple of ways depending on the draft version used
Draft 4
If using Draft 4 version, you can use schema composition and enum:
{
"type": "object",
"properties": {
"ticketType": {
"type": "integer"
},
"seat": {
"type": "integer"
}
},
"required": ["ticketType"],
"oneOf": [
{ "properties": { "ticketType": { "enum": [1] } } },
{ "properties": { "ticketType": { "enum": [2] }, "required": ["seat"] } }
]
}
The oneOf
keyword accepts a list of
subschemas
of which exactly one must pass.
If ticketType
is one of the enums in the
list [1]
(meaning, exactly
1
), there are no other requirements. If
it is 2
, there’s an additional
requirement for seat
property to exist.
Draft 7
If you’re using the newer Draft 7 version, you can use If-Then-Else and const:
{
"type": "object",
"properties": {
"ticketType": {
"type": "integer"
},
"seat": {
"type": "integer"
}
},
"required": ["ticketType"],
"if": {
"properties": {
"ticketType": {
"const": 2
}
}
},
"then": {
"required": ["seat"]
}
}
As there are no additional requirements for the else case, it can be omitted here.
I do like const more than enum in the case of these single values. I think it communicates it better than a single-item enum list.