Easily serialize dataclasses to and from JSON.
—
Strategies for handling JSON fields that don't correspond to dataclass fields during deserialization. The library provides three approaches: raising exceptions, ignoring extra fields, or capturing them in a designated catch-all field.
The Undefined enum defines the available strategies for handling undefined parameters.
class Undefined(Enum):
"""
Enumeration of strategies for handling undefined parameters during deserialization.
"""
INCLUDE = ... # Store undefined parameters in a CatchAll field
RAISE = ... # Raise UndefinedParameterError for any undefined parameters
EXCLUDE = ... # Ignore undefined parameters silentlyUsage example:
from dataclasses import dataclass
from dataclasses_json import dataclass_json, Undefined
@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class Person:
name: str
age: int
# JSON with extra field - will be ignored
json_data = '{"name": "Alice", "age": 30, "extra_field": "ignored"}'
person = Person.from_json(json_data) # Works fine, extra_field ignoredThe CatchAll type annotation designates a field to receive undefined parameters when using Undefined.INCLUDE.
# Type variable for catch-all fields (bound to Mapping)
CatchAllVar = TypeVar("CatchAllVar", bound=Mapping)
CatchAll = Optional[CatchAllVar]Usage example:
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, Undefined, CatchAll
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class FlexibleData:
name: str
age: int
# This field will receive all undefined parameters
extra_data: CatchAll = field(default_factory=dict)
# JSON with extra fields
json_data = '{"name": "Bob", "age": 25, "city": "NYC", "country": "USA"}'
data = FlexibleData.from_json(json_data)
print(data.name) # "Bob"
print(data.age) # 25
print(data.extra_data) # {"city": "NYC", "country": "USA"}Exception raised when undefined parameters are encountered with Undefined.RAISE strategy.
class UndefinedParameterError(Exception):
"""
Raised when undefined parameters are encountered and the current
undefined parameter handling strategy is set to RAISE.
Inherits from marshmallow.exceptions.ValidationError for consistency
with the validation system.
"""Usage example:
from dataclasses import dataclass
from dataclasses_json import dataclass_json, Undefined, UndefinedParameterError
@dataclass_json(undefined=Undefined.RAISE)
@dataclass
class StrictData:
name: str
age: int
try:
# This will raise an exception due to extra field
json_data = '{"name": "Charlie", "age": 35, "invalid_field": "error"}'
data = StrictData.from_json(json_data)
except UndefinedParameterError as e:
print(f"Undefined parameter error: {e}")Silently ignores any fields in the JSON that don't correspond to dataclass fields.
@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class Product:
name: str
price: float
# Extra fields are ignored
json_data = '{"name": "Widget", "price": 19.99, "discontinued": true, "vendor": "ACME"}'
product = Product.from_json(json_data)
# Only name and price are used, other fields ignoredBenefits:
Use cases:
Throws UndefinedParameterError for any undefined fields, ensuring strict data validation.
@dataclass_json(undefined=Undefined.RAISE)
@dataclass
class StrictConfig:
host: str
port: int
ssl_enabled: bool
# This will raise an exception
try:
json_data = '{"host": "localhost", "port": 8080, "ssl_enabled": true, "timeout": 30}'
config = StrictConfig.from_json(json_data)
except UndefinedParameterError:
print("Strict validation failed - unexpected field found")Benefits:
Use cases:
Captures undefined parameters in a designated CatchAll field for later processing or storage.
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class ExtensibleRecord:
id: str
name: str
# All undefined fields stored here
metadata: CatchAll = field(default_factory=dict)
json_data = '{"id": "123", "name": "Test", "tags": ["a", "b"], "priority": "high"}'
record = ExtensibleRecord.from_json(json_data)
print(record.id) # "123"
print(record.name) # "Test"
print(record.metadata) # {"tags": ["a", "b"], "priority": "high"}
# Can access undefined data
print(record.metadata.get('priority')) # "high"Benefits:
Use cases:
You can specify undefined behavior at the field level using the config function:
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config, Undefined
@dataclass_json
@dataclass
class MixedHandling:
name: str
# This field has custom undefined handling
settings: dict = field(metadata=config(undefined=Undefined.RAISE))Only one CatchAll field is allowed per dataclass. Attempting to define multiple will raise an error:
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class InvalidMultipleCatchAll:
name: str
catch_all_1: CatchAll = field(default_factory=dict)
catch_all_2: CatchAll = field(default_factory=dict) # Error!The CatchAll field must be properly typed and have a default:
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class ValidCatchAll:
name: str
# Correct: Optional dict with default
extra: CatchAll = field(default_factory=dict)
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class InvalidCatchAll:
name: str
# Error: Missing default value
extra: CatchAllUndefined parameter handling works seamlessly with marshmallow schema validation:
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class ValidatedFlexible:
name: str = field(metadata=config(mm_field=fields.String(required=True)))
age: int = field(metadata=config(mm_field=fields.Integer(validate=lambda x: x >= 0)))
extra: CatchAll = field(default_factory=dict)
# Schema validation still applies to defined fields
schema = ValidatedFlexible.schema()
try:
# This will fail validation (negative age)
result = schema.loads('{"name": "Test", "age": -5, "city": "NYC"}')
except ValidationError:
print("Validation failed for defined fields")When serializing back to JSON, the behavior depends on the strategy used during deserialization:
@dataclass_json(undefined=Undefined.INCLUDE)
@dataclass
class RoundTrip:
name: str
extra: CatchAll = field(default_factory=dict)
# Original JSON
original = '{"name": "Test", "city": "NYC", "tags": ["a", "b"]}'
obj = RoundTrip.from_json(original)
# Serialization includes captured fields
serialized = obj.to_json()
# Contains: {"name": "Test", "city": "NYC", "tags": ["a", "b"]}
# Round-trip preservation
restored = RoundTrip.from_json(serialized)
assert restored.extra == {"city": "NYC", "tags": ["a", "b"]}Install with Tessl CLI
npx tessl i tessl/pypi-dataclasses-json