0
# Union Handling and Type Checking
1
2
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.
3
4
## Capabilities
5
6
### Union Tagging Strategies
7
8
Different strategies for serializing union types to distinguish between different type variants.
9
10
```python { .api }
11
class ExternalTagging:
12
"""
13
Default tagging strategy that wraps union values in a dictionary with type tag.
14
Format: {"TypeName": value}
15
"""
16
17
class InternalTagging:
18
"""
19
Internal tagging strategy that adds a type tag field inside the serialized object.
20
21
Parameters:
22
- tag: Field name for the type tag (default: "type")
23
"""
24
def __init__(self, tag: str = "type"): ...
25
26
class AdjacentTagging:
27
"""
28
Adjacent tagging strategy that places type tag and content in separate fields.
29
30
Parameters:
31
- tag: Field name for the type tag (default: "type")
32
- content: Field name for the content (default: "content")
33
"""
34
def __init__(self, tag: str = "type", content: str = "content"): ...
35
36
class Untagged:
37
"""
38
Untagged strategy that attempts to deserialize based on structure matching.
39
Note: Can be ambiguous and slower than tagged approaches.
40
"""
41
42
# Type aliases for convenience
43
DefaultTagging = ExternalTagging
44
Tagging = Union[ExternalTagging, InternalTagging, AdjacentTagging, Untagged]
45
```
46
47
### Type Checking Modes
48
49
Configurable type checking behavior for serialization and deserialization.
50
51
```python { .api }
52
class TypeCheck:
53
"""Base class for type checking configuration."""
54
55
# Pre-defined type checking modes
56
strict: TypeCheck # Strict type checking (default) - raises errors on type mismatches
57
disabled: TypeCheck # Disable type checking - accept any compatible values
58
coerce: TypeCheck # Coerce types when possible - attempt conversion between compatible types
59
```
60
61
## Usage Examples
62
63
### External Tagging (Default)
64
65
```python
66
from serde import serde, to_dict, from_dict
67
from typing import Union
68
69
@serde
70
class Dog:
71
name: str
72
breed: str
73
74
@serde
75
class Cat:
76
name: str
77
indoor: bool
78
79
@serde
80
class Pet:
81
animal: Union[Dog, Cat]
82
83
dog = Pet(Dog("Buddy", "Golden Retriever"))
84
data = to_dict(dog)
85
# {'animal': {'Dog': {'name': 'Buddy', 'breed': 'Golden Retriever'}}}
86
87
cat = Pet(Cat("Whiskers", True))
88
data = to_dict(cat)
89
# {'animal': {'Cat': {'name': 'Whiskers', 'indoor': True}}}
90
```
91
92
### Internal Tagging
93
94
```python
95
from serde import serde, to_dict, from_dict, InternalTagging
96
from typing import Union
97
98
@serde(tagging=InternalTagging("type"))
99
class Shape:
100
area: Union[Circle, Rectangle]
101
102
@serde
103
class Circle:
104
radius: float
105
106
@serde
107
class Rectangle:
108
width: float
109
height: float
110
111
shape = Shape(Circle(5.0))
112
data = to_dict(shape)
113
# {'area': {'type': 'Circle', 'radius': 5.0}}
114
115
shape = Shape(Rectangle(10.0, 20.0))
116
data = to_dict(shape)
117
# {'area': {'type': 'Rectangle', 'width': 10.0, 'height': 20.0}}
118
```
119
120
### Adjacent Tagging
121
122
```python
123
from serde import serde, to_dict, AdjacentTagging
124
from typing import Union
125
126
@serde(tagging=AdjacentTagging("kind", "data"))
127
class Message:
128
payload: Union[TextMessage, ImageMessage]
129
130
@serde
131
class TextMessage:
132
text: str
133
134
@serde
135
class ImageMessage:
136
url: str
137
alt_text: str
138
139
message = Message(TextMessage("Hello World"))
140
data = to_dict(message)
141
# {'payload': {'kind': 'TextMessage', 'data': {'text': 'Hello World'}}}
142
143
message = Message(ImageMessage("image.jpg", "A photo"))
144
data = to_dict(message)
145
# {'payload': {'kind': 'ImageMessage', 'data': {'url': 'image.jpg', 'alt_text': 'A photo'}}}
146
```
147
148
### Untagged Unions
149
150
```python
151
from serde import serde, to_dict, Untagged
152
from typing import Union
153
154
@serde(tagging=Untagged)
155
class Data:
156
value: Union[int, str, list]
157
158
# pyserde will attempt to match structure during deserialization
159
data1 = Data(42)
160
serialized = to_dict(data1) # {'value': 42}
161
162
data2 = Data("hello")
163
serialized = to_dict(data2) # {'value': 'hello'}
164
165
data3 = Data([1, 2, 3])
166
serialized = to_dict(data3) # {'value': [1, 2, 3]}
167
```
168
169
### Type Checking Modes
170
171
```python
172
from serde import serde, to_dict, from_dict, strict, disabled, coerce
173
174
# Strict type checking (default)
175
@serde(type_check=strict)
176
class StrictUser:
177
name: str
178
age: int
179
180
# This will raise an error
181
try:
182
user = from_dict(StrictUser, {"name": "Alice", "age": "30"}) # age is string, not int
183
except Exception as e:
184
print(f"Strict mode error: {e}")
185
186
# Disabled type checking
187
@serde(type_check=disabled)
188
class FlexibleUser:
189
name: str
190
age: int
191
192
# This will accept the string value
193
user = from_dict(FlexibleUser, {"name": "Alice", "age": "30"})
194
# FlexibleUser(name='Alice', age='30') # age remains as string
195
196
# Coercing type checking
197
@serde(type_check=coerce)
198
class CoercingUser:
199
name: str
200
age: int
201
202
# This will convert string to int
203
user = from_dict(CoercingUser, {"name": "Alice", "age": "30"})
204
# CoercingUser(name='Alice', age=30) # age converted to int
205
```
206
207
### Optional Types
208
209
```python
210
from serde import serde, to_dict, from_dict
211
from typing import Optional
212
213
@serde
214
class User:
215
name: str
216
email: Optional[str] = None
217
age: Optional[int] = None
218
219
# Optional fields can be omitted
220
user1 = User("Alice")
221
data = to_dict(user1)
222
# {'name': 'Alice', 'email': None, 'age': None}
223
224
# Or provided with values
225
user2 = User("Bob", "bob@example.com", 25)
226
data = to_dict(user2)
227
# {'name': 'Bob', 'email': 'bob@example.com', 'age': 25}
228
229
# Deserialization handles missing optional fields
230
data = {"name": "Charlie"}
231
user3 = from_dict(User, data)
232
# User(name='Charlie', email=None, age=None)
233
```
234
235
### Complex Union Types
236
237
```python
238
from serde import serde, to_dict, from_dict
239
from typing import Union, List, Dict
240
241
@serde
242
class ApiResponse:
243
data: Union[str, int, List[str], Dict[str, any]]
244
success: bool
245
246
# String response
247
response1 = ApiResponse("Operation successful", True)
248
data = to_dict(response1)
249
# {'data': {'str': 'Operation successful'}, 'success': True}
250
251
# List response
252
response2 = ApiResponse(["item1", "item2", "item3"], True)
253
data = to_dict(response2)
254
# {'data': {'List[str]': ['item1', 'item2', 'item3']}, 'success': True}
255
256
# Dictionary response
257
response3 = ApiResponse({"key": "value", "count": 42}, True)
258
data = to_dict(response3)
259
# {'data': {'Dict[str, typing.Any]': {'key': 'value', 'count': 42}}, 'success': True}
260
```