This guide explains how JSONSchex interprets $schema and $vocabulary, and how those settings influence validation and compilation.
Overview
$schemaidentifies the JSON Schema dialect in use (e.g., Draft 2020-12).$vocabularylists the vocabularies enabled by the meta-schema.- JSONSchex uses these signals to determine which keywords are allowed and how to interpret them.
Dialect resolution
JSONSchex resolves dialect in this order:
- If the root schema contains
$schemaand anexternal_loaderis provided, JSONSchex attempts to load the meta-schema at that URI. - If the meta-schema loads successfully, JSONSchex reads
$vocabularyfrom it to build the enabled vocabulary set. - If no loader is available or the meta-schema cannot be loaded, JSONSchex proceeds with the default vocabulary set.
$vocabulary Semantics
The $vocabulary object maps vocabulary URIs to a boolean:
truemeans the vocabulary is required. If JSONSchex does not support it, compilation fails.falsemeans the vocabulary is optional. If unsupported, it is ignored.
This behavior matches the Draft 2020-12 specification.
Default Vocabularies
When no meta-schema can be loaded, JSONSchex uses its default vocabulary list. This includes the following 8 vocabulary URIs for Draft 2020-12:
https://json-schema.org/draft/2020-12/vocab/corehttps://json-schema.org/draft/2020-12/vocab/applicatorhttps://json-schema.org/draft/2020-12/vocab/validationhttps://json-schema.org/draft/2020-12/vocab/unevaluatedhttps://json-schema.org/draft/2020-12/vocab/format-annotationhttps://json-schema.org/draft/2020-12/vocab/format-assertionhttps://json-schema.org/draft/2020-12/vocab/contenthttps://json-schema.org/draft/2020-12/vocab/meta-data
This default set provides reasonable behavior for local schemas without remote meta-schema access.
You can access these programmatically:
iex> JSONSchex.Vocabulary.defaults()
[
"https://json-schema.org/draft/2020-12/vocab/core",
"https://json-schema.org/draft/2020-12/vocab/applicator",
"https://json-schema.org/draft/2020-12/vocab/validation",
"https://json-schema.org/draft/2020-12/vocab/unevaluated",
"https://json-schema.org/draft/2020-12/vocab/format-annotation",
"https://json-schema.org/draft/2020-12/vocab/format-assertion",
"https://json-schema.org/draft/2020-12/vocab/content",
"https://json-schema.org/draft/2020-12/vocab/meta-data"
]Common outcomes
- Missing required vocabulary: compilation fails with an unsupported vocabulary error.
- Optional vocabulary unsupported: compilation continues, but those keywords are ignored.
- No loader available:
$schemais not resolved remotely; defaults are used.
Examples
Example 1: Schema with explicit vocabulary
Here's a schema with explicit $schema and $vocabulary:
# This schema requires the core and validation vocabularies
meta_schema = %{
"$schema" => "https://json-schema.org/draft/2020-12/schema",
"$vocabulary" => %{
"https://json-schema.org/draft/2020-12/vocab/core" => true,
"https://json-schema.org/draft/2020-12/vocab/validation" => true,
"https://example.com/custom-vocab" => false # Optional, will be ignored if unsupported
}
}
# With supported vocabularies - compilation succeeds
{:ok, compiled} = JSONSchex.compile(meta_schema)
# If a required vocabulary is unsupported - compilation fails
unsupported_schema = %{
"$vocabulary" => %{
"https://example.com/unsupported" => true # Required but not supported
}
}
{:error, msg} = JSONSchex.compile(unsupported_schema)
# msg will indicate the unsupported vocabularyExample 2: Using a custom meta-schema
You can create a custom meta-schema that restricts or extends the available vocabularies. Here's an example that only allows core and validation vocabularies:
# Define a restricted meta-schema
restricted_meta = %{
"$schema" => "https://json-schema.org/draft/2020-12/schema",
"$id" => "https://myapp.example.com/restricted-meta",
"$vocabulary" => %{
"https://json-schema.org/draft/2020-12/vocab/core" => true,
"https://json-schema.org/draft/2020-12/vocab/validation" => true,
# All other vocabularies are not listed, so they're disabled
}
}
# Loader that provides our custom meta-schema
loader = fn
"https://myapp.example.com/restricted-meta" -> {:ok, restricted_meta}
_other_uri -> {:error, "Schema not found"}
end
# Schema that uses the custom meta-schema
schema = %{
"$schema" => "https://myapp.example.com/restricted-meta",
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"}
},
# This would be ignored because applicator vocabulary is not enabled
"allOf" => [%{"minProperties" => 1}]
}
# Compile with the custom loader
{:ok, compiled} = JSONSchex.compile(schema, external_loader: loader)
# The schema compiles, but 'allOf' is ignored because the applicator
# vocabulary is not in the restricted meta-schema's vocabulary listExample 3: Format assertion through vocabulary
The format vocabulary has two modes:
# Meta-schema with format as annotation only
annotation_meta = %{
"$vocabulary" => %{
"https://json-schema.org/draft/2020-12/vocab/core" => true,
"https://json-schema.org/draft/2020-12/vocab/format-annotation" => true
}
}
# Meta-schema with format as assertion
assertion_meta = %{
"$vocabulary" => %{
"https://json-schema.org/draft/2020-12/vocab/core" => true,
"https://json-schema.org/draft/2020-12/vocab/format-assertion" => true
}
}
# The compile-time option overrides the vocabulary setting
schema = %{"type" => "string", "format" => "email"}
# Annotation mode - format is not validated
{:ok, compiled1} = JSONSchex.compile(schema, format_assertion: false)
JSONSchex.validate(compiled1, "not-an-email") # => :ok
# Assertion mode - format is validated
{:ok, compiled2} = JSONSchex.compile(schema, format_assertion: true)
JSONSchex.validate(compiled2, "not-an-email") # => {:error, [...]}Practical guidance
- If you rely on a custom meta-schema or vocabulary, provide an
external_loader. - Use
$schemato make the dialect explicit and predictable. - Avoid mixing keywords from unsupported vocabularies unless you also ship a loader that resolves the meta-schema.