0
# Data Utilities
1
2
FastAPI provides utility functions for data encoding and serialization, particularly useful for converting Python objects to JSON-compatible formats. These utilities are essential for handling complex data types that aren't natively JSON serializable, such as datetime objects, Pydantic models, and database models.
3
4
## Capabilities
5
6
### JSON Encodable Converter
7
8
Primary utility function for converting Python objects to JSON-compatible formats with extensive customization options.
9
10
```python { .api }
11
def jsonable_encoder(
12
obj: Any,
13
include: Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] = None,
14
exclude: Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] = None,
15
by_alias: bool = True,
16
exclude_unset: bool = False,
17
exclude_defaults: bool = False,
18
exclude_none: bool = False,
19
round_trip: bool = True,
20
timedelta_isoformat: str = "iso8601",
21
sqlalchemy_safe: bool = True,
22
fallback: Callable[[Any], Any] = None,
23
) -> Any:
24
"""
25
Convert any object to a JSON-compatible format.
26
27
Parameters:
28
- obj: The object to encode
29
- include: Fields to include (set of field names or dict with nested structure)
30
- exclude: Fields to exclude (set of field names or dict with nested structure)
31
- by_alias: Use field aliases if available (Pydantic models)
32
- exclude_unset: Exclude fields that weren't explicitly set
33
- exclude_defaults: Exclude fields that contain their default value
34
- exclude_none: Exclude fields with None values
35
- round_trip: Enable round-trip serialization compatibility
36
- timedelta_isoformat: Format for timedelta objects ("iso8601" or "float")
37
- sqlalchemy_safe: Safe handling of SQLAlchemy models
38
- fallback: Custom fallback function for unsupported types
39
40
Returns:
41
JSON-compatible object (dict, list, str, int, float, bool, None)
42
"""
43
```
44
45
## Usage Examples
46
47
### Basic Object Encoding
48
49
```python
50
from fastapi.encoders import jsonable_encoder
51
from datetime import datetime, date, timedelta
52
from decimal import Decimal
53
from uuid import UUID, uuid4
54
55
# Basic Python types
56
data = {
57
"string": "hello",
58
"integer": 42,
59
"float": 3.14,
60
"boolean": True,
61
"none": None,
62
"list": [1, 2, 3],
63
"dict": {"nested": "value"}
64
}
65
66
encoded = jsonable_encoder(data)
67
print(encoded) # Already JSON-compatible, returned as-is
68
69
# Complex types that need encoding
70
complex_data = {
71
"datetime": datetime.now(),
72
"date": date.today(),
73
"timedelta": timedelta(hours=2, minutes=30),
74
"decimal": Decimal("10.50"),
75
"uuid": uuid4(),
76
"bytes": b"binary data",
77
"set": {1, 2, 3}
78
}
79
80
encoded_complex = jsonable_encoder(complex_data)
81
print(encoded_complex)
82
# {
83
# "datetime": "2024-01-15T10:30:00.123456",
84
# "date": "2024-01-15",
85
# "timedelta": "PT2H30M",
86
# "decimal": 10.5,
87
# "uuid": "123e4567-e89b-12d3-a456-426614174000",
88
# "bytes": "YmluYXJ5IGRhdGE=", # base64 encoded
89
# "set": [1, 2, 3]
90
# }
91
```
92
93
### Pydantic Model Encoding
94
95
```python
96
from fastapi.encoders import jsonable_encoder
97
from pydantic import BaseModel, Field
98
from typing import Optional
99
from datetime import datetime
100
101
class User(BaseModel):
102
id: int
103
name: str
104
email: str
105
full_name: Optional[str] = Field(None, alias="fullName")
106
created_at: datetime
107
is_active: bool = True
108
109
class UserProfile(BaseModel):
110
user: User
111
preferences: dict
112
login_count: int = 0
113
114
# Create model instances
115
user = User(
116
id=1,
117
name="john_doe",
118
email="john@example.com",
119
fullName="John Doe",
120
created_at=datetime.now()
121
)
122
123
profile = UserProfile(
124
user=user,
125
preferences={"theme": "dark", "notifications": True}
126
)
127
128
# Encode with default settings
129
encoded = jsonable_encoder(profile)
130
print(encoded)
131
132
# Encode using aliases
133
encoded_with_aliases = jsonable_encoder(profile, by_alias=True)
134
print(encoded_with_aliases["user"]["fullName"]) # Uses alias
135
136
# Exclude certain fields
137
encoded_minimal = jsonable_encoder(
138
profile,
139
exclude={"user": {"created_at"}, "login_count"}
140
)
141
142
# Include only specific fields
143
encoded_specific = jsonable_encoder(
144
profile,
145
include={"user": {"id", "name"}, "preferences"}
146
)
147
148
# Exclude unset fields
149
user_partial = User(id=2, name="jane", email="jane@example.com")
150
encoded_no_unset = jsonable_encoder(user_partial, exclude_unset=True)
151
# Won't include created_at if it wasn't explicitly set
152
153
# Exclude None values
154
user_with_none = User(
155
id=3,
156
name="bob",
157
email="bob@example.com",
158
full_name=None, # Explicitly set to None
159
created_at=datetime.now()
160
)
161
encoded_no_none = jsonable_encoder(user_with_none, exclude_none=True)
162
```
163
164
### Database Model Integration
165
166
```python
167
from fastapi import FastAPI, Depends
168
from fastapi.encoders import jsonable_encoder
169
from sqlalchemy.orm import Session
170
# Assuming SQLAlchemy models
171
172
app = FastAPI()
173
174
@app.get("/users/{user_id}")
175
def get_user(user_id: int, db: Session = Depends(get_db)):
176
user = db.query(UserModel).filter(UserModel.id == user_id).first()
177
178
if not user:
179
raise HTTPException(status_code=404, detail="User not found")
180
181
# Convert SQLAlchemy model to JSON-compatible dict
182
user_data = jsonable_encoder(user, sqlalchemy_safe=True)
183
return user_data
184
185
@app.get("/users/{user_id}/profile")
186
def get_user_profile(user_id: int, db: Session = Depends(get_db)):
187
user = db.query(UserModel).filter(UserModel.id == user_id).first()
188
189
if not user:
190
raise HTTPException(status_code=404, detail="User not found")
191
192
# Include related data, exclude sensitive fields
193
profile_data = jsonable_encoder(
194
user,
195
include={
196
"id", "name", "email", "created_at", "last_login",
197
"profile": {"bio", "location", "website"},
198
"posts": {"id", "title", "created_at"}
199
},
200
exclude={"password_hash", "email_verified_token"}
201
)
202
return profile_data
203
```
204
205
### Custom Fallback Function
206
207
```python
208
from fastapi.encoders import jsonable_encoder
209
import numpy as np
210
from typing import Any
211
212
# Custom fallback for handling NumPy arrays
213
def numpy_fallback(obj: Any) -> Any:
214
if isinstance(obj, np.ndarray):
215
return obj.tolist()
216
elif isinstance(obj, np.integer):
217
return int(obj)
218
elif isinstance(obj, np.floating):
219
return float(obj)
220
else:
221
# Re-raise the TypeError to use default handling
222
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
223
224
# Data with NumPy objects
225
data_with_numpy = {
226
"array": np.array([1, 2, 3, 4]),
227
"matrix": np.array([[1, 2], [3, 4]]),
228
"int64": np.int64(42),
229
"float64": np.float64(3.14159)
230
}
231
232
encoded_numpy = jsonable_encoder(data_with_numpy, fallback=numpy_fallback)
233
print(encoded_numpy)
234
# {
235
# "array": [1, 2, 3, 4],
236
# "matrix": [[1, 2], [3, 4]],
237
# "int64": 42,
238
# "float64": 3.14159
239
# }
240
```
241
242
### Response Encoding in Endpoints
243
244
```python
245
from fastapi import FastAPI, HTTPException
246
from fastapi.encoders import jsonable_encoder
247
from fastapi.responses import JSONResponse
248
249
app = FastAPI()
250
251
@app.get("/complex-data")
252
def get_complex_data():
253
complex_response = {
254
"timestamp": datetime.now(),
255
"data": {
256
"measurements": [
257
{"value": Decimal("123.456"), "unit": "kg"},
258
{"value": Decimal("789.012"), "unit": "kg"}
259
],
260
"metadata": {
261
"id": uuid4(),
262
"processed": True
263
}
264
}
265
}
266
267
# Encode the complex data structure
268
encoded_response = jsonable_encoder(complex_response)
269
return JSONResponse(content=encoded_response)
270
271
@app.post("/process-data")
272
def process_data(raw_data: dict):
273
# Process and potentially modify data
274
processed = {
275
"original": raw_data,
276
"processed_at": datetime.now(),
277
"result": calculate_result(raw_data),
278
"metadata": {
279
"version": "1.0",
280
"processor_id": uuid4()
281
}
282
}
283
284
# Use jsonable_encoder to ensure everything is JSON-compatible
285
return jsonable_encoder(processed, exclude_none=True)
286
```
287
288
### Time Delta Formatting Options
289
290
```python
291
from fastapi.encoders import jsonable_encoder
292
from datetime import timedelta
293
294
duration_data = {
295
"short": timedelta(minutes=30),
296
"medium": timedelta(hours=2, minutes=45),
297
"long": timedelta(days=5, hours=3, minutes=20)
298
}
299
300
# ISO 8601 format (default)
301
iso_encoded = jsonable_encoder(duration_data, timedelta_isoformat="iso8601")
302
print(iso_encoded)
303
# {
304
# "short": "PT30M",
305
# "medium": "PT2H45M",
306
# "long": "P5DT3H20M"
307
# }
308
309
# Float format (total seconds)
310
float_encoded = jsonable_encoder(duration_data, timedelta_isoformat="float")
311
print(float_encoded)
312
# {
313
# "short": 1800.0,
314
# "medium": 9900.0,
315
# "long": 443400.0
316
# }
317
```
318
319
## Types
320
321
```python { .api }
322
from typing import Any, Callable, Dict, List, Optional, Set, Union
323
324
# Include/exclude type for field selection
325
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]
326
327
# Fallback function type
328
FallbackFunc = Callable[[Any], Any]
329
330
# Supported timedelta formats
331
TimedeltaIsoFormat = Union["iso8601", "float"]
332
```