Format Validators¶
JSON Schema format values describe semantic constraints such as email addresses, UUIDs, URIs, or
domain-specific identifiers. Pydantic JSON Schema only enforces those constraints when you pass a
matching entry in format_validators.
Without a matching validator, format is treated as metadata and the normal JSON Schema type still
applies.
Install Optional Dependencies¶
Format validator dependencies are optional. Install the group you need:
| Extra | Use for |
|---|---|
formats-base |
Standard JSON Schema formats |
formats-extra |
Domain-specific pydantic-extra-types types |
formats-all |
Both groups |
See Installation for the install commands.
Basic Usage¶
The key in format_validators must match the schema's format value.
from pydantic import ValidationError
from pydantic_jsonschema import Schema, to_model
from pydantic_jsonschema.formats import UUID, Email
schema = Schema.model_validate(
{
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"id": {"type": "string", "format": "uuid"},
},
}
)
LooseUser = to_model(schema)
loose_user = LooseUser(email="not-an-email", id="not-a-uuid")
print(loose_user.email)
#> not-an-email
StrictUser = to_model(
schema,
format_validators={
"email": Email,
"uuid": UUID,
},
)
strict_user = StrictUser(
email="alice@example.com",
id="550e8400-e29b-41d4-a716-446655440000",
)
print(type(strict_user.id).__name__)
#> UUID
try:
StrictUser(email="invalid", id="550e8400-e29b-41d4-a716-446655440000")
except ValidationError as er:
print(type(er).__name__)
#> ValidationError
Provided Format Aliases¶
The pydantic_jsonschema.formats module exports aliases that can be passed to
format_validators.
JSON Schema format |
Alias | Validates |
|---|---|---|
email |
Email |
Email addresses |
hostname |
Hostname |
RFC 1123 hostnames, including single labels like localhost |
uri |
Uri |
Absolute URIs with a scheme |
uri-reference |
UriReference |
Absolute or relative URI references |
iri |
Iri |
Absolute internationalized URIs |
iri-reference |
IriReference |
Absolute or relative internationalized URI references |
date |
Date |
Dates |
time |
Time |
Times |
date-time |
DateTime |
Date-time values |
duration |
Duration |
Durations |
uuid |
UUID |
UUID values |
ipv4 |
IPv4 |
IPv4 addresses |
ipv6 |
IPv6 |
IPv6 addresses |
from pydantic_jsonschema import Schema, to_model
from pydantic_jsonschema.formats import DateTime, Email, IPv4, Uri
schema = Schema.model_validate(
{
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"website": {"type": "string", "format": "uri"},
"created_at": {"type": "string", "format": "date-time"},
"ip": {"type": "string", "format": "ipv4"},
},
}
)
User = to_model(
schema,
format_validators={
"email": Email,
"uri": Uri,
"date-time": DateTime,
"ipv4": IPv4,
},
)
user = User(
email="alice@example.com",
website="https://example.com/profile",
created_at="2024-01-15T10:30:00Z",
ip="192.168.1.1",
)
print(type(user.created_at).__name__)
#> datetime
Third-Party Pydantic Types¶
The formats-extra extra installs pydantic-extra-types, but it does not automatically map schema
formats. Import the type you want and pass it through format_validators.
from pydantic_extra_types.color import Color
from pydantic_extra_types.payment import PaymentCardNumber
from pydantic_jsonschema import Schema, to_model
schema = Schema.model_validate(
{
"type": "object",
"properties": {
"card": {"type": "string", "format": "payment-card"},
"color": {"type": "string", "format": "color"},
},
}
)
Checkout = to_model(
schema,
format_validators={
"payment-card": PaymentCardNumber,
"color": Color,
},
)
checkout = Checkout(card="4111111111111111", color="#ff5733")
print(type(checkout.color).__name__)
#> Color
Common pydantic-extra-types validators include payment cards, colors, countries, coordinates,
MAC addresses, and phone numbers. See the
pydantic-extra-types documentation for the full
list.
Custom Validators¶
Use a callable when you want to validate or normalize a project-specific format.
Callable validators receive the raw input before Pydantic type coercion. They should return the
validated value and raise ValueError when validation fails.
from pydantic import ValidationError
from pydantic_jsonschema import Schema, to_model
def validate_sku(value: str) -> str:
parts = value.split("-")
if len(parts) != 3:
msg = "SKU must have 3 parts"
raise ValueError(msg)
if not parts[0].isalpha() or len(parts[0]) != 3:
msg = "First part must be 3 letters"
raise ValueError(msg)
if not parts[1].isdigit() or len(parts[1]) != 4:
msg = "Second part must be 4 digits"
raise ValueError(msg)
if not parts[2].isalpha() or len(parts[2]) != 3:
msg = "Third part must be 3 letters"
raise ValueError(msg)
return value.upper()
schema = Schema.model_validate(
{
"type": "object",
"properties": {
"sku": {"type": "string", "format": "sku"},
},
}
)
Product = to_model(schema, format_validators={"sku": validate_sku})
product = Product(sku="abc-1234-xyz")
print(product.sku)
#> ABC-1234-XYZ
try:
Product(sku="abc-12")
except ValidationError as er:
print(type(er).__name__)
#> ValidationError
Validator Types¶
format_validators accepts these validator forms:
| Validator form | Example | Behavior |
|---|---|---|
| Callable | {"sku": validate_sku} |
Called before Pydantic type validation |
| Pydantic type | {"email": EmailStr} |
Replaces the generated annotation |
Annotated type |
{"price": PositivePrice} |
Replaces the generated annotation and preserves its validators |
Callable Validators¶
from pydantic_jsonschema import JsonType
def normalize_phone(value: JsonType) -> str:
digits = "".join(character for character in str(value) if character.isdigit())
if len(digits) != 10:
msg = "Phone number must contain 10 digits"
raise ValueError(msg)
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
format_validators = {"phone": normalize_phone}
Pydantic Types¶
from pydantic import EmailStr, HttpUrl
from pydantic_extra_types.color import Color
format_validators = {
"color": Color,
"email": EmailStr,
"url": HttpUrl,
}
Annotated Types¶
from typing import Annotated
from pydantic import AfterValidator, Field
def round_price(value: float) -> float:
return round(value, 2)
PositivePrice = Annotated[
float,
Field(gt=0),
AfterValidator(round_price),
]
format_validators = {"price": PositivePrice}
Execution Order¶
Execution order depends on the validator form:
| Validator form | Order |
|---|---|
| Callable | format callable, then generated type validation |
| Pydantic type | Pydantic type validation |
Annotated type |
validators defined inside the Annotated type |
For callable validators, the value passed to the function is the raw input value. For Pydantic
types and Annotated types, the validator replaces the generated annotation, so Pydantic runs that
type's own validation pipeline.
Error Handling¶
Raise ValueError with a clear message when a value is invalid:
def validate_age(value: int) -> int:
if value < 0:
msg = "Age cannot be negative"
raise ValueError(msg)
if value > 150:
msg = "Age seems unrealistic"
raise ValueError(msg)
return value
Pydantic converts the error into a ValidationError with field location information.
Best Practices¶
- Keep validators focused on one format.
- Return the original or normalized value.
- Raise
ValueErrorfor invalid values. - Include valid and invalid cases in tests.
- Document the expected input format in examples and error messages.
Next Steps¶
- Installation - Install optional format validator dependencies
- Converters - Learn how schema conversion works
- Examples - Run complete examples