Skip to main content

JSON Serialization Customization

JSON serialization in this codebase is primarily controlled through the CoreConfig class defined in pydantic-core/python/pydantic_core/core_schema.py. This configuration allows you to define how complex Python types—specifically temporal objects, binary data, and special floating-point values—are represented when converted to JSON strings via the SchemaSerializer.

Temporal Types

The serialization of datetime, date, time, and timedelta objects is managed by two main configuration keys: ser_json_temporal and ser_json_timedelta.

Global Temporal Control

The ser_json_temporal setting is the primary switch for all date and time-related types. It supports three modes:

  • 'iso8601': (Default) Serializes objects to standard ISO 8601 strings (e.g., "2024-01-01T00:00:00").
  • 'seconds': Serializes objects to a Unix timestamp (float) representing seconds.
  • 'milliseconds': Serializes objects to a Unix timestamp (float) representing milliseconds.

When ser_json_temporal is set, it takes precedence over the more specific ser_json_timedelta setting.

from pydantic_core import SchemaSerializer, core_schema
from datetime import datetime

# Configuring serialization to milliseconds
s = SchemaSerializer(
core_schema.datetime_schema(),
config={'ser_json_temporal': 'milliseconds'}
)

dt = datetime(2024, 1, 1)
# Output is a float timestamp in milliseconds
assert s.to_json(dt) == b'1704067200000.0'

Timedelta Specifics

The ser_json_timedelta setting specifically targets timedelta objects. It allows for:

  • 'iso8601': (Default) Serializes to ISO 8601 duration strings (e.g., "P1DT2H").
  • 'float': Serializes to a total number of seconds.

In the pydantic_core.core_schema.CoreConfig definition, this parameter is noted as being ignored if ser_json_temporal is also present, as the latter provides a unified format for all temporal types.

Binary Data

The BytesSchema handles the validation and serialization of bytes objects. Because JSON does not have a native binary type, the ser_json_bytes configuration in CoreConfig determines the encoding strategy:

  • 'utf8': (Default) Attempts to decode bytes as a UTF-8 string. This will fail during serialization if the bytes contain non-UTF-8 compatible data.
  • 'base64': Encodes bytes into a Base64 string. This is the safest option for arbitrary binary data.
  • 'hex': Encodes bytes into a hexadecimal string.
from pydantic_core import SchemaSerializer, core_schema

# Using base64 for binary safety
s = SchemaSerializer(
core_schema.bytes_schema(),
config={'ser_json_bytes': 'base64'}
)

assert s.to_json(b'foobar') == b'"Zm9vYmFy"'

Complementing this is val_json_bytes, which defines how the serializer should expect bytes to be encoded when parsing JSON back into Python objects, ensuring symmetry between serialization and validation.

Special Float Values

Standard JSON (RFC 8259) does not support Infinity, -Infinity, or NaN. The ser_json_inf_nan setting in CoreConfig provides three strategies for handling these values when they appear in float fields:

  • 'null': (Default) Converts inf and nan to the JSON null literal. This is the most compatible with standard JSON parsers.
  • 'strings': Converts values to their string representations: "Infinity", "-Infinity", or "NaN".
  • 'constants': Produces the bare literals Infinity, -Infinity, and NaN. Note that this results in invalid JSON according to the strict specification, though some parsers (like Python's json module) may accept it.
from pydantic_core import SchemaSerializer, core_schema

# Serializing special floats as strings for compatibility
s = SchemaSerializer(
core_schema.float_schema(),
config={'ser_json_inf_nan': 'strings'}
)

assert s.to_json(float('inf')) == b'"Infinity"'
assert s.to_json(float('nan')) == b'"NaN"'

These settings are applied globally to the SchemaSerializer instance and affect all relevant fields within the schema tree, from simple float fields to complex nested models.