Easily serialize dataclasses to and from JSON.
—
Package-wide settings for custom encoders, decoders, and marshmallow fields that apply across all dataclasses unless overridden at the field level. Global configuration provides a centralized way to define type-specific serialization behavior.
The global_config object provides package-wide settings that affect all dataclass serialization unless overridden.
class _GlobalConfig:
"""
Global configuration singleton for package-wide serialization settings.
"""
encoders: Dict[Union[type, Optional[type]], Callable]
decoders: Dict[Union[type, Optional[type]], Callable]
mm_fields: Dict[Union[type, Optional[type]], marshmallow.fields.Field]
global_config: _GlobalConfigThe global configuration object contains three main dictionaries:
Register custom encoders for specific types that will be used across all dataclasses.
# Encoder function signature
EncoderFunc = Callable[[Any], Any] # Python value -> JSON-serializable valueUsage example:
from dataclasses import dataclass
from dataclasses_json import dataclass_json, global_config
from datetime import date, datetime
from decimal import Decimal
import uuid
# Register global encoders
global_config.encoders[date] = lambda d: d.isoformat()
global_config.encoders[datetime] = lambda dt: dt.isoformat()
global_config.encoders[Decimal] = str
global_config.encoders[uuid.UUID] = str
@dataclass_json
@dataclass
class Document:
id: uuid.UUID
title: str
created_date: date
modified_time: datetime
price: Decimal
# All instances automatically use global encoders
doc = Document(
id=uuid.uuid4(),
title="Sample Document",
created_date=date.today(),
modified_time=datetime.now(),
price=Decimal("19.99")
)
json_str = doc.to_json()
# Uses global encoders for UUID, date, datetime, and DecimalRegister custom decoders for converting JSON values back to Python types.
# Decoder function signature
DecoderFunc = Callable[[Any], Any] # JSON value -> Python valueUsage example:
from datetime import date, datetime
from decimal import Decimal
import uuid
# Register global decoders
global_config.decoders[date] = lambda s: datetime.fromisoformat(s).date()
global_config.decoders[datetime] = datetime.fromisoformat
global_config.decoders[Decimal] = Decimal
global_config.decoders[uuid.UUID] = uuid.UUID
# Now all dataclasses can deserialize these types automatically
json_data = '''{
"id": "123e4567-e89b-12d3-a456-426614174000",
"title": "Sample Document",
"created_date": "2023-12-01",
"modified_time": "2023-12-01T10:30:00",
"price": "19.99"
}'''
doc = Document.from_json(json_data)
# Automatically converts strings back to proper typesRegister marshmallow fields for types that require validation or complex transformation.
from marshmallow import fields
# Common marshmallow field types
Field = fields.FieldUsage example:
from marshmallow import fields
from datetime import datetime
from decimal import Decimal
# Register global marshmallow fields
global_config.mm_fields[datetime] = fields.DateTime(format='iso')
global_config.mm_fields[Decimal] = fields.Decimal(places=2, rounding='ROUND_HALF_UP')
global_config.mm_fields[str] = fields.String(validate=lambda x: len(x.strip()) > 0)
@dataclass_json
@dataclass
class Order:
timestamp: datetime
amount: Decimal
description: str
# Schema validation uses global mm_fields
schema = Order.schema()
# This will validate using global field definitions
try:
order = schema.loads('{"timestamp": "2023-12-01T10:30:00", "amount": "19.999", "description": ""}')
except ValidationError as e:
print("Validation failed:", e) # Empty description fails validationConfiguration follows a clear precedence hierarchy:
Example demonstrating precedence:
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config, global_config
from datetime import datetime
# Global configuration
global_config.encoders[datetime] = lambda dt: dt.strftime('%Y-%m-%d')
@dataclass_json
@dataclass
class Event:
# Uses global encoder (YYYY-MM-DD format)
start_time: datetime
# Field-level config overrides global (ISO format)
end_time: datetime = field(metadata=config(
encoder=lambda dt: dt.isoformat()
))
event = Event(datetime.now(), datetime.now())
json_str = event.to_json()
# start_time uses global encoder, end_time uses field-level encoderConfigure encoders for base classes that apply to all subclasses:
from abc import ABC, abstractmethod
class SerializableEntity(ABC):
@abstractmethod
def to_dict(self):
pass
# Global encoder for all SerializableEntity subclasses
global_config.encoders[SerializableEntity] = lambda obj: obj.to_dict()
class User(SerializableEntity):
def __init__(self, name: str):
self.name = name
def to_dict(self):
return {"name": self.name}
@dataclass_json
@dataclass
class Document:
author: User # Uses global SerializableEntity encoder
title: strHandle Optional types explicitly:
from typing import Optional
from datetime import datetime
# Configure for Optional[datetime]
global_config.encoders[Optional[datetime]] = lambda dt: dt.isoformat() if dt else None
global_config.decoders[Optional[datetime]] = lambda s: datetime.fromisoformat(s) if s else None
@dataclass_json
@dataclass
class Task:
name: str
due_date: Optional[datetime] = None # Uses Optional[datetime] configurationConfigure based on runtime environment:
import os
from datetime import datetime
# Development vs Production encoders
if os.getenv('ENV') == 'development':
# Verbose format for debugging
global_config.encoders[datetime] = lambda dt: {
'timestamp': dt.isoformat(),
'readable': dt.strftime('%Y-%m-%d %H:%M:%S'),
'timezone': str(dt.tzinfo)
}
else:
# Compact format for production
global_config.encoders[datetime] = lambda dt: dt.timestamp()Validate global configuration at startup:
def validate_global_config():
"""Validate that all global encoders have corresponding decoders."""
encoder_types = set(global_config.encoders.keys())
decoder_types = set(global_config.decoders.keys())
missing_decoders = encoder_types - decoder_types
if missing_decoders:
raise ValueError(f"Missing decoders for types: {missing_decoders}")
# Call during application initialization
validate_global_config()Save and restore configuration state:
def backup_global_config():
"""Create a backup of current global configuration."""
return {
'encoders': global_config.encoders.copy(),
'decoders': global_config.decoders.copy(),
'mm_fields': global_config.mm_fields.copy()
}
def restore_global_config(backup):
"""Restore global configuration from backup."""
global_config.encoders.clear()
global_config.encoders.update(backup['encoders'])
global_config.decoders.clear()
global_config.decoders.update(backup['decoders'])
global_config.mm_fields.clear()
global_config.mm_fields.update(backup['mm_fields'])
# Usage in tests
def test_with_custom_config():
backup = backup_global_config()
try:
# Modify global config for test
global_config.encoders[str] = lambda s: s.upper()
# ... run test ...
finally:
restore_global_config(backup)Global configuration lookups are performed for every field serialization. For optimal performance:
from functools import lru_cache
# Cache expensive transformations
@lru_cache(maxsize=1000)
def expensive_encoder(value):
# Complex transformation logic
return transformed_value
global_config.encoders[ComplexType] = expensive_encoderInstall with Tessl CLI
npx tessl i tessl/pypi-dataclasses-json