0
# Undefined Parameter Handling
1
2
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.
3
4
## Capabilities
5
6
### Undefined Behavior Enum
7
8
The `Undefined` enum defines the available strategies for handling undefined parameters.
9
10
```python { .api }
11
class Undefined(Enum):
12
"""
13
Enumeration of strategies for handling undefined parameters during deserialization.
14
"""
15
INCLUDE = ... # Store undefined parameters in a CatchAll field
16
RAISE = ... # Raise UndefinedParameterError for any undefined parameters
17
EXCLUDE = ... # Ignore undefined parameters silently
18
```
19
20
Usage example:
21
22
```python
23
from dataclasses import dataclass
24
from dataclasses_json import dataclass_json, Undefined
25
26
@dataclass_json(undefined=Undefined.EXCLUDE)
27
@dataclass
28
class Person:
29
name: str
30
age: int
31
32
# JSON with extra field - will be ignored
33
json_data = '{"name": "Alice", "age": 30, "extra_field": "ignored"}'
34
person = Person.from_json(json_data) # Works fine, extra_field ignored
35
```
36
37
### CatchAll Field Type
38
39
The `CatchAll` type annotation designates a field to receive undefined parameters when using `Undefined.INCLUDE`.
40
41
```python { .api }
42
# Type variable for catch-all fields (bound to Mapping)
43
CatchAllVar = TypeVar("CatchAllVar", bound=Mapping)
44
CatchAll = Optional[CatchAllVar]
45
```
46
47
Usage example:
48
49
```python
50
from dataclasses import dataclass, field
51
from dataclasses_json import dataclass_json, Undefined, CatchAll
52
53
@dataclass_json(undefined=Undefined.INCLUDE)
54
@dataclass
55
class FlexibleData:
56
name: str
57
age: int
58
# This field will receive all undefined parameters
59
extra_data: CatchAll = field(default_factory=dict)
60
61
# JSON with extra fields
62
json_data = '{"name": "Bob", "age": 25, "city": "NYC", "country": "USA"}'
63
data = FlexibleData.from_json(json_data)
64
65
print(data.name) # "Bob"
66
print(data.age) # 25
67
print(data.extra_data) # {"city": "NYC", "country": "USA"}
68
```
69
70
### Undefined Parameter Exception
71
72
Exception raised when undefined parameters are encountered with `Undefined.RAISE` strategy.
73
74
```python { .api }
75
class UndefinedParameterError(Exception):
76
"""
77
Raised when undefined parameters are encountered and the current
78
undefined parameter handling strategy is set to RAISE.
79
80
Inherits from marshmallow.exceptions.ValidationError for consistency
81
with the validation system.
82
"""
83
```
84
85
Usage example:
86
87
```python
88
from dataclasses import dataclass
89
from dataclasses_json import dataclass_json, Undefined, UndefinedParameterError
90
91
@dataclass_json(undefined=Undefined.RAISE)
92
@dataclass
93
class StrictData:
94
name: str
95
age: int
96
97
try:
98
# This will raise an exception due to extra field
99
json_data = '{"name": "Charlie", "age": 35, "invalid_field": "error"}'
100
data = StrictData.from_json(json_data)
101
except UndefinedParameterError as e:
102
print(f"Undefined parameter error: {e}")
103
```
104
105
## Undefined Handling Strategies
106
107
### EXCLUDE Strategy (Default)
108
109
Silently ignores any fields in the JSON that don't correspond to dataclass fields.
110
111
```python
112
@dataclass_json(undefined=Undefined.EXCLUDE)
113
@dataclass
114
class Product:
115
name: str
116
price: float
117
118
# Extra fields are ignored
119
json_data = '{"name": "Widget", "price": 19.99, "discontinued": true, "vendor": "ACME"}'
120
product = Product.from_json(json_data)
121
# Only name and price are used, other fields ignored
122
```
123
124
Benefits:
125
- Robust against API changes that add new fields
126
- Simple and predictable behavior
127
- No additional storage overhead
128
129
Use cases:
130
- Consuming third-party APIs that may add fields
131
- Backward compatibility when removing fields from dataclass
132
- Simple data models where extra data isn't needed
133
134
### RAISE Strategy
135
136
Throws `UndefinedParameterError` for any undefined fields, ensuring strict data validation.
137
138
```python
139
@dataclass_json(undefined=Undefined.RAISE)
140
@dataclass
141
class StrictConfig:
142
host: str
143
port: int
144
ssl_enabled: bool
145
146
# This will raise an exception
147
try:
148
json_data = '{"host": "localhost", "port": 8080, "ssl_enabled": true, "timeout": 30}'
149
config = StrictConfig.from_json(json_data)
150
except UndefinedParameterError:
151
print("Strict validation failed - unexpected field found")
152
```
153
154
Benefits:
155
- Catches data model mismatches early
156
- Ensures complete control over data structure
157
- Helps identify API contract violations
158
159
Use cases:
160
- Configuration files where unexpected fields indicate errors
161
- Internal APIs with strict contracts
162
- Data validation scenarios requiring complete control
163
164
### INCLUDE Strategy
165
166
Captures undefined parameters in a designated `CatchAll` field for later processing or storage.
167
168
```python
169
@dataclass_json(undefined=Undefined.INCLUDE)
170
@dataclass
171
class ExtensibleRecord:
172
id: str
173
name: str
174
# All undefined fields stored here
175
metadata: CatchAll = field(default_factory=dict)
176
177
json_data = '{"id": "123", "name": "Test", "tags": ["a", "b"], "priority": "high"}'
178
record = ExtensibleRecord.from_json(json_data)
179
180
print(record.id) # "123"
181
print(record.name) # "Test"
182
print(record.metadata) # {"tags": ["a", "b"], "priority": "high"}
183
184
# Can access undefined data
185
print(record.metadata.get('priority')) # "high"
186
```
187
188
Benefits:
189
- Preserves all data from JSON input
190
- Allows processing of dynamic or unknown fields
191
- Enables round-trip serialization without data loss
192
193
Use cases:
194
- Document stores with flexible schemas
195
- Plugin systems with dynamic configuration
196
- Data migration scenarios
197
- APIs with optional extension fields
198
199
## Advanced Usage Patterns
200
201
### Per-Field Undefined Handling
202
203
You can specify undefined behavior at the field level using the `config` function:
204
205
```python
206
from dataclasses import dataclass, field
207
from dataclasses_json import dataclass_json, config, Undefined
208
209
@dataclass_json
210
@dataclass
211
class MixedHandling:
212
name: str
213
# This field has custom undefined handling
214
settings: dict = field(metadata=config(undefined=Undefined.RAISE))
215
```
216
217
### Multiple CatchAll Fields
218
219
Only one `CatchAll` field is allowed per dataclass. Attempting to define multiple will raise an error:
220
221
```python
222
@dataclass_json(undefined=Undefined.INCLUDE)
223
@dataclass
224
class InvalidMultipleCatchAll:
225
name: str
226
catch_all_1: CatchAll = field(default_factory=dict)
227
catch_all_2: CatchAll = field(default_factory=dict) # Error!
228
```
229
230
### CatchAll Field Validation
231
232
The CatchAll field must be properly typed and have a default:
233
234
```python
235
@dataclass_json(undefined=Undefined.INCLUDE)
236
@dataclass
237
class ValidCatchAll:
238
name: str
239
# Correct: Optional dict with default
240
extra: CatchAll = field(default_factory=dict)
241
242
@dataclass_json(undefined=Undefined.INCLUDE)
243
@dataclass
244
class InvalidCatchAll:
245
name: str
246
# Error: Missing default value
247
extra: CatchAll
248
```
249
250
### Combining with Schema Validation
251
252
Undefined parameter handling works seamlessly with marshmallow schema validation:
253
254
```python
255
@dataclass_json(undefined=Undefined.INCLUDE)
256
@dataclass
257
class ValidatedFlexible:
258
name: str = field(metadata=config(mm_field=fields.String(required=True)))
259
age: int = field(metadata=config(mm_field=fields.Integer(validate=lambda x: x >= 0)))
260
extra: CatchAll = field(default_factory=dict)
261
262
# Schema validation still applies to defined fields
263
schema = ValidatedFlexible.schema()
264
try:
265
# This will fail validation (negative age)
266
result = schema.loads('{"name": "Test", "age": -5, "city": "NYC"}')
267
except ValidationError:
268
print("Validation failed for defined fields")
269
```
270
271
### Serialization Behavior
272
273
When serializing back to JSON, the behavior depends on the strategy used during deserialization:
274
275
```python
276
@dataclass_json(undefined=Undefined.INCLUDE)
277
@dataclass
278
class RoundTrip:
279
name: str
280
extra: CatchAll = field(default_factory=dict)
281
282
# Original JSON
283
original = '{"name": "Test", "city": "NYC", "tags": ["a", "b"]}'
284
obj = RoundTrip.from_json(original)
285
286
# Serialization includes captured fields
287
serialized = obj.to_json()
288
# Contains: {"name": "Test", "city": "NYC", "tags": ["a", "b"]}
289
290
# Round-trip preservation
291
restored = RoundTrip.from_json(serialized)
292
assert restored.extra == {"city": "NYC", "tags": ["a", "b"]}
293
```