Yet another serialization library on top of dataclasses
—
Advanced type system features for handling union types with different tagging strategies and configurable type checking modes. pyserde provides flexible approaches to serialize and deserialize union types and optional values.
Different strategies for serializing union types to distinguish between different type variants.
class ExternalTagging:
"""
Default tagging strategy that wraps union values in a dictionary with type tag.
Format: {"TypeName": value}
"""
class InternalTagging:
"""
Internal tagging strategy that adds a type tag field inside the serialized object.
Parameters:
- tag: Field name for the type tag (default: "type")
"""
def __init__(self, tag: str = "type"): ...
class AdjacentTagging:
"""
Adjacent tagging strategy that places type tag and content in separate fields.
Parameters:
- tag: Field name for the type tag (default: "type")
- content: Field name for the content (default: "content")
"""
def __init__(self, tag: str = "type", content: str = "content"): ...
class Untagged:
"""
Untagged strategy that attempts to deserialize based on structure matching.
Note: Can be ambiguous and slower than tagged approaches.
"""
# Type aliases for convenience
DefaultTagging = ExternalTagging
Tagging = Union[ExternalTagging, InternalTagging, AdjacentTagging, Untagged]Configurable type checking behavior for serialization and deserialization.
class TypeCheck:
"""Base class for type checking configuration."""
# Pre-defined type checking modes
strict: TypeCheck # Strict type checking (default) - raises errors on type mismatches
disabled: TypeCheck # Disable type checking - accept any compatible values
coerce: TypeCheck # Coerce types when possible - attempt conversion between compatible typesfrom serde import serde, to_dict, from_dict
from typing import Union
@serde
class Dog:
name: str
breed: str
@serde
class Cat:
name: str
indoor: bool
@serde
class Pet:
animal: Union[Dog, Cat]
dog = Pet(Dog("Buddy", "Golden Retriever"))
data = to_dict(dog)
# {'animal': {'Dog': {'name': 'Buddy', 'breed': 'Golden Retriever'}}}
cat = Pet(Cat("Whiskers", True))
data = to_dict(cat)
# {'animal': {'Cat': {'name': 'Whiskers', 'indoor': True}}}from serde import serde, to_dict, from_dict, InternalTagging
from typing import Union
@serde(tagging=InternalTagging("type"))
class Shape:
area: Union[Circle, Rectangle]
@serde
class Circle:
radius: float
@serde
class Rectangle:
width: float
height: float
shape = Shape(Circle(5.0))
data = to_dict(shape)
# {'area': {'type': 'Circle', 'radius': 5.0}}
shape = Shape(Rectangle(10.0, 20.0))
data = to_dict(shape)
# {'area': {'type': 'Rectangle', 'width': 10.0, 'height': 20.0}}from serde import serde, to_dict, AdjacentTagging
from typing import Union
@serde(tagging=AdjacentTagging("kind", "data"))
class Message:
payload: Union[TextMessage, ImageMessage]
@serde
class TextMessage:
text: str
@serde
class ImageMessage:
url: str
alt_text: str
message = Message(TextMessage("Hello World"))
data = to_dict(message)
# {'payload': {'kind': 'TextMessage', 'data': {'text': 'Hello World'}}}
message = Message(ImageMessage("image.jpg", "A photo"))
data = to_dict(message)
# {'payload': {'kind': 'ImageMessage', 'data': {'url': 'image.jpg', 'alt_text': 'A photo'}}}from serde import serde, to_dict, Untagged
from typing import Union
@serde(tagging=Untagged)
class Data:
value: Union[int, str, list]
# pyserde will attempt to match structure during deserialization
data1 = Data(42)
serialized = to_dict(data1) # {'value': 42}
data2 = Data("hello")
serialized = to_dict(data2) # {'value': 'hello'}
data3 = Data([1, 2, 3])
serialized = to_dict(data3) # {'value': [1, 2, 3]}from serde import serde, to_dict, from_dict, strict, disabled, coerce
# Strict type checking (default)
@serde(type_check=strict)
class StrictUser:
name: str
age: int
# This will raise an error
try:
user = from_dict(StrictUser, {"name": "Alice", "age": "30"}) # age is string, not int
except Exception as e:
print(f"Strict mode error: {e}")
# Disabled type checking
@serde(type_check=disabled)
class FlexibleUser:
name: str
age: int
# This will accept the string value
user = from_dict(FlexibleUser, {"name": "Alice", "age": "30"})
# FlexibleUser(name='Alice', age='30') # age remains as string
# Coercing type checking
@serde(type_check=coerce)
class CoercingUser:
name: str
age: int
# This will convert string to int
user = from_dict(CoercingUser, {"name": "Alice", "age": "30"})
# CoercingUser(name='Alice', age=30) # age converted to intfrom serde import serde, to_dict, from_dict
from typing import Optional
@serde
class User:
name: str
email: Optional[str] = None
age: Optional[int] = None
# Optional fields can be omitted
user1 = User("Alice")
data = to_dict(user1)
# {'name': 'Alice', 'email': None, 'age': None}
# Or provided with values
user2 = User("Bob", "bob@example.com", 25)
data = to_dict(user2)
# {'name': 'Bob', 'email': 'bob@example.com', 'age': 25}
# Deserialization handles missing optional fields
data = {"name": "Charlie"}
user3 = from_dict(User, data)
# User(name='Charlie', email=None, age=None)from serde import serde, to_dict, from_dict
from typing import Union, List, Dict
@serde
class ApiResponse:
data: Union[str, int, List[str], Dict[str, any]]
success: bool
# String response
response1 = ApiResponse("Operation successful", True)
data = to_dict(response1)
# {'data': {'str': 'Operation successful'}, 'success': True}
# List response
response2 = ApiResponse(["item1", "item2", "item3"], True)
data = to_dict(response2)
# {'data': {'List[str]': ['item1', 'item2', 'item3']}, 'success': True}
# Dictionary response
response3 = ApiResponse({"key": "value", "count": 42}, True)
data = to_dict(response3)
# {'data': {'Dict[str, typing.Any]': {'key': 'value', 'count': 42}}, 'success': True}Install with Tessl CLI
npx tessl i tessl/pypi-pyserde