Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

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.