or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-serialization.mdfield-configuration.mdformat-modules.mdindex.mdtype-system.mdunion-handling.md

union-handling.mddocs/

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

```