In the Previous Post

In the previous article, we covered the basics of JSON Schema for validating JSON data. In this article, we move to advanced usage: combining schemas and applying conditional logic.



Additional Ways to Validate JSON

Validating boolean values

Only true or false is allowed.

{
    "type": "boolean"
}

Validating values with enum

With the enum keyword, you can limit allowed values for a field. The schema below allows only “male” or “female”.

{
    "type": "string",
    "enum": ["male", "female"]
}

Validating with a constant value

With the const keyword, you can require a field to have a single fixed value.

{
    "properties": {
        "title": {
            "const": "MadPlay's MadLife."
        }
    }
}

With this schema, the title property must match the predefined string.

Validating null

If type is null, the data must be null.

{
    "type": "null"
}

The value must be exactly null. Even an empty string ("") is not accepted.



Composite Schemas

JSON Schema supports composition with the following keywords. You can list conditions as arrays and evaluate multiple schema rules together.

  • allOf: all schema validations must pass.
  • anyOf: one or more schema validations must pass.
  • oneOf: exactly one schema validation must pass.

allOf: all schemas must pass

The following schema accepts values that are at most 5 characters and start with a. So strings like "abc" and "a12a2" pass. Because no type is declared, numeric values can also pass.

{
  "allOf": [
    {
      "maxLength": 5
    },
    {
      "pattern": "^a"
    }
  ]
}

anyOf: one or more schemas

The schema below validates id when it is either a string or a number. If id is null or boolean, validation fails.

{
  "properties": {
    "id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "number"
        }
      ]
    }
  }
}

oneOf: exactly one schema

Only one condition in the list may pass. In this schema, the value "abc" for id fails because it matches both conditions (minimum length and starts with a).

{
  "properties": {
    "id": {
      "oneOf": [
        {
          "minLength": 2
        },
        {
          "pattern": "^a"
        }
      ]
    }
  }
}



Conditional Schemas

You can also apply conditions with not and if-then-else.

not

Unlike the previous rules, not allows only data that does not match the given schema. The schema below fails every string value.

{
    "not": {
      "type": "string"
    }
}

This pattern is uncommon, but use it carefully. For example, the rule below can never pass:

{
    "type": "string",
    "not": {
      "type": "string"
    }
}

It requires the field to be a string and also rejects all strings.

if-then-else

You can define branch logic with if, then, and else. The rule is straightforward:

  • If if matches, apply then.
  • If if does not match, apply else.

In the following schema, if the data is a string, it must have at least 3 characters and start with m. If it is not a string, the value must be 0.

{
  "if": {
    "type": "string"
  },
  "then": {
    "minLength": 3,
    "pattern": "^m"
  },
  "else": {
    "const": 0
  }
}

Examples:

  • "madplay": pass, string, length >= 3, starts with m.
  • 0: pass, not a string but equals 0.
  • 0.1.1: fail, invalid number.
  • ["mad"]: fail, neither string nor 0.



Improving Schema Reusability

Referencing schemas

$id gives a JSON schema a unique identity. It serves two common purposes.

The first is to declare a unique identifier, often as a download location at the top level:

{
    "$id": "http://foo.bar/custom/email-schema.json",

    "type": "string",
    "format": "email",
    "pattern": "@madplay\\.+"
}

The second is to define a base URI that $ref can target:

{
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
        },
        "email": {
            "$ref": "http://foo.bar/custom/email-schema.json"
        }
    }
}

Here, the email field reuses a referenced schema. Without $ref, the field would embed the full rule directly:

{
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
        },
        "email": {
            "type": "string",
            "format": "email",
            "pattern": "@madplay\\.+"
        }
    }
}

Defining shared schemas

The definitions keyword is commonly used with $ref for reusable validation blocks. The example below defines text content and photo content schemas once and reuses them.

Line breaks were reduced to keep the snippet shorter.

{
   "type":"object",
   "properties":{
      "textContents":{
         "type":"array",
         "items":[{ "$ref":"#/definitions/textContents" }]
      },
      "paperContents":{
         "type":"array",
         "items":[{ "$ref":"#/definitions/textContents" }]
      },
      "photoContents":{
         "type":"array",
         "items":[{ "$ref":"#/definitions/photoContent" }]
      }
   },
   "definitions":{
      "textContents":{
         "type":"object",
         "properties":{
            "id":{ "type":"string" },
            "type":{ "type":"string", "const":"text" }
         }
      },
      "photoContent":{
         "type":"object",
         "properties":{
            "id":{ "type":"string" },
            "type":{ "type":"string", "enum":[ "photo1", "photo2" ]},
            "imageUrl":{ "type":"string" }
         },
         "required":[ "imageUrl" ]
      }
   }
}


Next

In this post, we covered more advanced schema composition and schema reuse patterns. In the next post, we will implement a JSON Schema validator in Java code.