This guide explains how JSONSchex handles the content* vocabulary and the format keyword, including how to enable assertion behavior.
Overview
Content keywords are annotation-only by default. Enable checks with content_assertion: true when compiling the schema. format_assertion: true explicitly enables format checks; Draft 2020-12 can also enable them through an active format-assertion vocabulary.
Draft 7 supports contentEncoding and contentMediaType, but not contentSchema. Draft selection follows the dialect guide.
Content Vocabulary Assertions
Keywords
| Keyword | Draft 7 | Draft 2020-12 |
|---|---|---|
contentEncoding | Optional assertion | Optional assertion |
contentMediaType | Optional assertion | Optional assertion |
contentSchema | Unknown keyword; ignored | Validates decoded content when assertions are enabled |
Default behavior
By default, these keywords are annotations only. No decoding or parsing is performed.
Enabling assertions
Pass content_assertion: true when compiling:
{:ok, compiled} =
JSONSchex.compile(schema, content_assertion: true)Behavior when enabled
When content_assertion is enabled:
contentEncodingsupports:base64base64url
contentMediaTypesupports:application/json- Any media type ending in
+json
- Encoding and media type are asserted even when
contentSchemais absent. - In Draft 2020-12, a supported
contentSchemaalso validates the decoded and parsed value. - In Draft 7,
contentSchemais ignored even when assertions are enabled; it does not suppress encoding or media-type checks. - These checks apply only to strings. Use
"type" => "string"when other instance types must be rejected.
Error reporting
Errors are surfaced using the following rule identifiers:
:contentEncodingfor decoding failures:contentMediaTypefor media type parsing failures- The underlying schema rule for
contentSchemavalidation failures
Nested validation paths in Error.path are stored deepest-first. JSONSchex.format_error/1 renders them as root-first JSON Pointer paths.
Complete End-to-End Example
This Draft 2020-12 example checks base64-encoded JSON against a contentSchema:
# Define a schema with content assertions
schema = %{
"type" => "object",
"properties" => %{
"encodedData" => %{
"type" => "string",
"contentEncoding" => "base64",
"contentMediaType" => "application/json",
"contentSchema" => %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"},
"age" => %{"type" => "integer", "minimum" => 0}
},
"required" => ["name", "age"]
}
}
}
}
# Compile with content assertions enabled
{:ok, compiled} = JSONSchex.compile(schema, content_assertion: true)
# Valid case: properly encoded JSON that matches the content schema
valid_json = Jason.encode!(%{"name" => "Alice", "age" => 30})
valid_base64 = Base.encode64(valid_json)
valid_data = %{"encodedData" => valid_base64}
JSONSchex.validate(compiled, valid_data)
# => :ok
# Invalid case 1: Bad base64 encoding
invalid_base64 = %{"encodedData" => "not-valid-base64!!!"}
{:error, [error]} = JSONSchex.validate(compiled, invalid_base64)
# error.path => ["encodedData"]
# error.rule => :contentEncoding
# JSONSchex.format_error(error)
# => "At /encodedData: Failed to decode string as 'base64': \"not-valid-base64!!!\""
# Invalid case 2: Valid base64 but not JSON
non_json_base64 = Base.encode64("This is not JSON")
{:error, [error]} = JSONSchex.validate(compiled, %{"encodedData" => non_json_base64})
# error.path => ["encodedData"]
# error.rule => :contentMediaType
# JSONSchex.format_error(error)
# => "At /encodedData: Failed to decode as 'application/json': ..."
# Invalid case 3: Valid JSON but doesn't match contentSchema
invalid_json = Jason.encode!(%{"name" => "Bob", "age" => -5}) # age is negative
invalid_data = %{"encodedData" => Base.encode64(invalid_json)}
{:error, [error]} = JSONSchex.validate(compiled, invalid_data)
# error.path => ["age", "encodedData"]
# error.rule => :minimum
# JSONSchex.format_error(error)
# => "At /encodedData/age: Value -5 is less than minimum 0"
# Invalid case 4: Missing required field in content
incomplete_json = Jason.encode!(%{"name" => "Charlie"}) # missing age
incomplete_data = %{"encodedData" => Base.encode64(incomplete_json)}
{:error, [error]} = JSONSchex.validate(compiled, incomplete_data)
# error.path => ["encodedData"]
# error.rule => :required
# JSONSchex.format_error(error)
# => "At /encodedData: Missing required properties: age"Practical Use Cases
Content vocabulary assertions are useful for:
- API Responses — Validating that encoded payloads match expected schemas
- Data Storage — Ensuring stored encoded data is valid before saving
- Message Queues — Validating message payloads that are base64-encoded
- Configuration Files — Checking that embedded encoded configs are valid
Format assertions
Keyword
format
Default behavior
format is annotation-only in Draft 7 and in the canonical Draft 2020-12 dialect unless assertions are enabled.
For Draft 2020-12, an active format-assertion vocabulary also enables validation. The schema-less/custom-meta-schema fallback uses the implementation capability set, which includes that vocabulary. Declare the canonical $schema URI for the standard annotation-only defaults; format_assertion: false does not disable an active assertion vocabulary.
Enabling assertions
Pass format_assertion: true when compiling:
{:ok, compiled} =
JSONSchex.compile(schema, format_assertion: true)Supported Format Values
When format_assertion is enabled, the following format values are supported:
Date and Time Formats
date-time— RFC 3339 date-time (e.g.,"2024-01-15T10:30:00Z")date— RFC 3339 full-date (e.g.,"2024-01-15")time— RFC 3339 full-time (e.g.,"10:30:00Z")duration— ISO 8601 duration (e.g.,"P3Y6M4DT12H30M5S")
Email Formats
email— Email address (e.g.,"user@example.com")idn-email— Internationalized email address (requiresidnadependency)
Hostname Formats
hostname— DNS hostname (e.g.,"example.com")idn-hostname— Internationalized hostname (requiresidnadependency)
IP Address Formats
ipv4— IPv4 address (e.g.,"192.168.1.1")ipv6— IPv6 address (e.g.,"2001:0db8:85a3::8a2e:0370:7334")
URI Formats
uri— Absolute URI with scheme (e.g.,"https://example.com/path")uri-reference— URI reference (absolute or relative, e.g.,"/path/to/resource")iri— Internationalized Resource Identifieriri-reference— IRI reference (absolute or relative)uri-template— URI template (RFC 6570, e.g.,"https://api.example.com/users/{id}")
Identifier Formats
uuid— UUID (e.g.,"550e8400-e29b-41d4-a716-446655440000")
JSON Pointer Formats
json-pointer— JSON Pointer (e.g.,"/foo/bar/0")relative-json-pointer— Relative JSON Pointer (e.g.,"1/foo")
Pattern Format
regex— ECMA-262 regular expression
Behavior when enabled
When format_assertion is enabled:
- JSONSchex validates data against the format registry.
- Unknown format values pass validation by default (per the specification).
- Errors are returned with
:formatrule identifier.
Format Validation Examples
# Email validation
email_schema = %{"type" => "string", "format" => "email"}
{:ok, compiled} = JSONSchex.compile(email_schema, format_assertion: true)
JSONSchex.validate(compiled, "user@example.com")
# => :ok
{:error, [error]} = JSONSchex.validate(compiled, "not-an-email")
# error.rule => :format
# JSONSchex.format_error(error)
# => "Invalid email format: \"not-an-email\""
# UUID validation
uuid_schema = %{"type" => "string", "format" => "uuid"}
{:ok, compiled} = JSONSchex.compile(uuid_schema, format_assertion: true)
JSONSchex.validate(compiled, "550e8400-e29b-41d4-a716-446655440000")
# => :ok
{:error, [error]} = JSONSchex.validate(compiled, "not-a-uuid")
# error.rule => :format
# JSONSchex.format_error(error)
# => "Invalid uuid format: \"not-a-uuid\""
# Date-time validation
datetime_schema = %{"type" => "string", "format" => "date-time"}
{:ok, compiled} = JSONSchex.compile(datetime_schema, format_assertion: true)
JSONSchex.validate(compiled, "2024-01-15T10:30:00Z")
# => :ok
{:error, [error]} = JSONSchex.validate(compiled, "not a date")
# error.rule => :format
# JSONSchex.format_error(error)
# => "Invalid date-time format: \"not a date\""
# IPv4 validation
ipv4_schema = %{"type" => "string", "format" => "ipv4"}
{:ok, compiled} = JSONSchex.compile(ipv4_schema, format_assertion: true)
JSONSchex.validate(compiled, "192.168.1.1")
# => :ok
{:error, [error]} = JSONSchex.validate(compiled, "999.999.999.999")
# error.rule => :format
# JSONSchex.format_error(error)
# => "Invalid ipv4 format: \"999.999.999.999\""Practical guidance
- Enable assertions only when you need strict validation; annotation-only mode is faster and more permissive.
- If you validate user input that must conform to content or format constraints, enable the relevant assertion flags.
- You can enable both
content_assertionandformat_assertionin the same compile call.
# Enable both content and format assertions
{:ok, compiled} = JSONSchex.compile(
schema,
content_assertion: true,
format_assertion: true
)