There are cases when you want to reuse validations that are specific only to that schema document, or you simply want to group multiple sub-schemas together. For example, we have a custom email validator, and a custom username validator, and we want to apply those validators multiple times. This can be easily achieved using $ref keyword and the $defs keyword.

In drafts 06 and 07 $defs keyword was named definitions, this has changed starting with draft 2019-09. Don’t worry, definitions can still be used (you can use any name, it doesn’t really matter).

{
  "type": "object",
  "properties": {
    "username": {"$ref": "#/$defs/custom-username"},
    "aliases": {
      "type": "array",
      "items": {"$ref": "#/$defs/custom-username"}
    },
    "primary_email": {"$ref": "#/$defs/custom-email"},
    "other_emails": {
      "type": "array",
      "items": {"$ref": "#/$defs/custom-email"}
    }
  },
  
  "$defs": {
    "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 #/$defs/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 /$defs/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: $defs and custom-email.

Descending into $defs 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": "#/$defs/personal"
    }
  },
   
  "$defs": {
    "email": {
      "type": "string",
      "format": "email"    
    },
    "personal": {
      "type": "object",
      "properties": {
        "mail": {
          "$ref": "#/$defs/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": "#/$defs/friend"
    }
  },
  
  "$defs": {
    "friend": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "friends": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/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"]
  }
}