Definitions
definitions keyword
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"]
}
}