There are cases when you want to reuse validations that are specific only to that schema document. For example, we have a custom email validator, and a custom username validator, and we want to apply those validators multiple times. Outside the schema document these validators aren’t useful. This can be easily achieved using $ref keyword and the definitions keyword.

{
  "type": "object",
  "properties": {
    "username": {"$ref": "#/definitions/custom-username"},
    "aliases": {
      "type": "array",
      "items": {"$ref": "#/definitions/custom-username"}
    },
    "primary_email": {"$ref": "#/definitions/custom-email"},
    "other_emails": {
      "type": "array",
      "items": {"$ref": "#/definitions/custom-email"}
    }
  },
  
  "definitions": {
    "custom-username": {
      "type": "string",
      "minLength":3
    },
    "custom-email": {
      "type": "string",
      "format": "email",
      "pattern": "\\.com$"
    }
  }
}
Input Status
{"username": "opis", "primary_email": "opis@example.com"} valid
{"aliases": ["opis json schema", "opis the lib"]} valid
{"other_emails": ["opis@example.com", "opis.lib@example.com"]} valid
{"username": "ab", "primary_email": "opis@example.test"} invalid
{"aliases": ["opis", "ab"]} invalid
{"other_email": ["opis@example.test"]} invalid

Ok, let’s see what happens there. The confusing thing is the value of the $ref keyword, which is something like this #/definitions/something. That’s an URI fragment (starts with #), and the rest of the string after the # represents a JSON pointer. JSON pointers are covered in the next chapter, but we still explain the behaviour in a few words, using our example.

Consider this json pointer /definitions/custom-email. Because the pointer starts with / (slash) we know that we begin at the root of the schema document. Every substring delimited by a / slash, will be used as property name (key) to descend. In our case we have two substrings: definitions and custom-email.

Descending into definitions gives us

{
  "custom-username": {
    "type": "string",
    "minLength":3
  },
  "custom-email": {
    "type": "string",
    "format": "email",
    "pattern": "\\.com$"
  }
}

And from here, descending into custom-email gives us

{
  "type": "string",
  "format": "email",
  "pattern": "\\.com$"
}

Now, this is the value given by our json pointer.

Examples

Definition referencing other definition

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "personal_data": {
      "$ref": "#/definitions/personal"
    }
  },
   
  "definitions": {
    "email": {
      "type": "string",
      "format": "email"    
    },
    "personal": {
      "type": "object",
      "properties": {
        "mail": {
          "$ref": "#/definitions/email"
        }
      }
    }
  }
}
Input Status
{"name": "John", "personal_data": {"mail": "john@example.com"}} valid
{"name": "John", "personal_data": {"mail": "invalid-email"}} invalid
{"name": "John", "personal_data": "john@example.com"} invalid

Recursive validation

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "best_friend": {
      "$ref": "#/definitions/friend"
    }
  },
  
  "definitions": {
    "friend": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "friends": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/friend"
          }
        }
      }
    }
  }
}
{
  "name": "John",
  "best_friend": {
    "name": "The dog"
  }
}
{
  "name": "John",
  "best_friend": {
    "name": "The dog",
    "friends": [
      {
        "name": "The neighbor's dog",
        "friends": [
          {
            "name": "Underdog"
          },
          {
            "name": "Scooby-Doo"
          }
        ]
      }
    ]
  }
}
{
  "name": "John",
  "best_friend": "The dog"
}
{
  "name": "John",
  "best_friend": {
    "name": "The dog",
    "friends": ["Underdog", "Scooby-Doo"]
  }
}