0
# Serializers
1
2
Django REST Framework serializers provide a powerful system for converting complex data types to native Python datatypes and vice versa. The type stubs provide comprehensive type safety for all serializer operations, field definitions, and validation patterns.
3
4
## Core Serializer Classes
5
6
### BaseSerializer
7
8
```python { .api }
9
class BaseSerializer(Field[Any, Any, Any, _IN]):
10
"""Base class for all serializers with generic instance type support."""
11
12
partial: bool
13
many: bool
14
instance: _IN | None
15
initial_data: Any
16
17
def __init__(
18
self,
19
instance: _IN | None = None,
20
data: Any = empty,
21
**kwargs: Any
22
) -> None: ...
23
24
def is_valid(self, *, raise_exception: bool = False) -> bool: ...
25
def save(self, **kwargs: Any) -> _IN: ...
26
def create(self, validated_data: Any) -> _IN: ...
27
def update(self, instance: _IN, validated_data: Any) -> _IN: ...
28
def to_representation(self, instance: _IN) -> Any: ...
29
```
30
31
**Parameters:**
32
- `instance: _IN | None` - Object instance to serialize or update
33
- `data: Any` - Input data for deserialization
34
- `partial: bool` - Allow partial updates when True
35
- `many: bool` - Serialize multiple objects when True
36
- `**kwargs: Any` - Additional serializer options
37
38
### Serializer
39
40
```python { .api }
41
class Serializer(BaseSerializer[_IN]):
42
"""Main serializer class for custom serializations."""
43
44
def __init__(
45
self,
46
instance: _IN | None = None,
47
data: Any = empty,
48
many: bool = False,
49
partial: bool = False,
50
context: dict[str, Any] | None = None,
51
allow_empty: bool = False,
52
**kwargs: Any
53
) -> None: ...
54
55
def get_fields(self) -> dict[str, Field]: ...
56
def validate(self, attrs: Any) -> Any: ...
57
def to_representation(self, instance: _IN) -> dict[str, Any]: ...
58
```
59
60
**Parameters:**
61
- `context: dict[str, Any] | None` - Additional context for serialization
62
- `allow_empty: bool` - Allow empty values when True
63
64
### ModelSerializer
65
66
```python { .api }
67
class ModelSerializer(Serializer[_MT]):
68
"""Serializer that automatically generates fields from Django models."""
69
70
serializer_field_mapping: dict[type[models.Field], type[Field]]
71
72
class Meta:
73
model: type[_MT]
74
fields: Sequence[str] | Literal["__all__"]
75
exclude: Sequence[str] | None
76
read_only_fields: Sequence[str] | None
77
extra_kwargs: dict[str, dict[str, Any]]
78
depth: int | None
79
80
def get_field_names(self, declared_fields: dict[str, Field], info: Any) -> list[str]: ...
81
def build_field(self, field_name: str, info: Any, model_class: type[Model], nested_depth: int) -> tuple[type[Field], dict[str, Any]]: ...
82
def build_standard_field(self, field_name: str, model_field: models.Field) -> tuple[type[Field], dict[str, Any]]: ...
83
def build_relational_field(self, field_name: str, relation_info: Any) -> tuple[type[Field], dict[str, Any]]: ...
84
def build_nested_field(self, field_name: str, relation_info: Any, nested_depth: int) -> tuple[type[Field], dict[str, Any]]: ...
85
def create(self, validated_data: dict[str, Any]) -> _MT: ...
86
def update(self, instance: _MT, validated_data: dict[str, Any]) -> _MT: ...
87
```
88
89
**Meta Class Parameters:**
90
- `model: type[_MT]` - Django model class to serialize
91
- `fields: Sequence[str] | Literal["__all__"]` - Fields to include ('__all__' or field list)
92
- `exclude: Sequence[str] | None` - Fields to exclude from serialization
93
- `read_only_fields: Sequence[str] | None` - Fields that cannot be modified
94
- `extra_kwargs: dict[str, dict[str, Any]]` - Additional field arguments
95
- `depth: int | None` - Depth of nested serialization (default: None)
96
97
### HyperlinkedModelSerializer
98
99
```python { .api }
100
class HyperlinkedModelSerializer(ModelSerializer[_MT]):
101
"""Model serializer that uses hyperlinked relationships."""
102
103
serializer_related_field: type[HyperlinkedRelatedField]
104
serializer_url_field: type[HyperlinkedIdentityField]
105
url_field_name: str
106
107
class Meta(ModelSerializer.Meta):
108
pass
109
```
110
111
### ListSerializer
112
113
```python { .api }
114
class ListSerializer(BaseSerializer[_IN]):
115
"""Serializer for handling multiple object instances."""
116
117
child: Field | BaseSerializer | None
118
many: bool
119
allow_empty: bool | None
120
max_length: int | None
121
min_length: int | None
122
123
def __init__(
124
self,
125
*args: Any,
126
child: BaseSerializer | None = None,
127
allow_empty: bool = True,
128
max_length: int | None = None,
129
min_length: int | None = None,
130
**kwargs: Any
131
) -> None: ...
132
133
def create(self, validated_data: list[dict[str, Any]]) -> list[_IN]: ...
134
def update(self, instance: list[_IN], validated_data: list[dict[str, Any]]) -> list[_IN]: ...
135
```
136
137
**Parameters:**
138
- `child: BaseSerializer | None` - Serializer for individual items
139
- `allow_empty: bool` - Allow empty lists when True
140
- `max_length: int | None` - Maximum number of items
141
- `min_length: int | None` - Minimum number of items
142
143
## Serializer Configuration
144
145
### Field Declaration
146
147
```python { .api }
148
from rest_framework import serializers
149
150
class BookSerializer(serializers.ModelSerializer[Book]):
151
# Explicit field declarations
152
isbn = serializers.CharField(max_length=13, required=True)
153
rating = serializers.FloatField(min_value=0.0, max_value=5.0)
154
tags = serializers.ListField(child=serializers.CharField())
155
156
# Method fields
157
author_name = serializers.SerializerMethodField()
158
159
class Meta:
160
model = Book
161
fields = ['id', 'title', 'isbn', 'rating', 'tags', 'author_name']
162
read_only_fields = ['id']
163
164
def get_author_name(self, obj: Book) -> str:
165
return f"{obj.author.first_name} {obj.author.last_name}"
166
```
167
168
### Nested Serialization
169
170
```python { .api }
171
class AuthorSerializer(serializers.ModelSerializer[Author]):
172
class Meta:
173
model = Author
174
fields = ['id', 'first_name', 'last_name', 'bio']
175
176
class BookSerializer(serializers.ModelSerializer[Book]):
177
author = AuthorSerializer(read_only=True)
178
author_id = serializers.IntegerField(write_only=True)
179
180
class Meta:
181
model = Book
182
fields = ['id', 'title', 'author', 'author_id', 'published_date']
183
184
def create(self, validated_data: dict[str, Any]) -> Book:
185
author_id = validated_data.pop('author_id')
186
author = Author.objects.get(id=author_id)
187
return Book.objects.create(author=author, **validated_data)
188
```
189
190
### Custom Serializer
191
192
```python { .api }
193
class BookSearchSerializer(serializers.Serializer):
194
"""Custom serializer for search functionality."""
195
196
query = serializers.CharField(max_length=100)
197
author = serializers.CharField(max_length=50, required=False)
198
genre = serializers.ChoiceField(choices=['fiction', 'non-fiction', 'mystery'], required=False)
199
published_after = serializers.DateField(required=False)
200
min_rating = serializers.FloatField(min_value=0.0, max_value=5.0, required=False)
201
202
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
203
"""Cross-field validation."""
204
if attrs.get('min_rating', 0) > 4.5 and not attrs.get('author'):
205
raise serializers.ValidationError(
206
"High rating searches require an author filter"
207
)
208
return attrs
209
210
def validate_query(self, value: str) -> str:
211
"""Field-level validation."""
212
if len(value.strip()) < 2:
213
raise serializers.ValidationError("Query must be at least 2 characters")
214
return value.strip()
215
```
216
217
## Validation Capabilities
218
219
### Field-Level Validation
220
221
```python { .api }
222
class UserSerializer(serializers.ModelSerializer[User]):
223
class Meta:
224
model = User
225
fields = ['username', 'email', 'password']
226
extra_kwargs = {
227
'password': {'write_only': True, 'min_length': 8}
228
}
229
230
def validate_username(self, value: str) -> str:
231
"""Validate username field."""
232
if User.objects.filter(username__iexact=value).exists():
233
raise serializers.ValidationError("Username already exists")
234
if not value.replace('_', '').replace('-', '').isalnum():
235
raise serializers.ValidationError("Username can only contain letters, numbers, - and _")
236
return value
237
238
def validate_email(self, value: str) -> str:
239
"""Validate email field."""
240
if User.objects.filter(email__iexact=value).exists():
241
raise serializers.ValidationError("Email already registered")
242
return value.lower()
243
```
244
245
### Object-Level Validation
246
247
```python { .api }
248
class EventSerializer(serializers.ModelSerializer[Event]):
249
class Meta:
250
model = Event
251
fields = ['name', 'start_date', 'end_date', 'max_attendees', 'current_attendees']
252
read_only_fields = ['current_attendees']
253
254
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
255
"""Cross-field validation."""
256
start_date = attrs.get('start_date')
257
end_date = attrs.get('end_date')
258
259
if start_date and end_date:
260
if start_date >= end_date:
261
raise serializers.ValidationError(
262
"End date must be after start date"
263
)
264
265
if start_date < timezone.now().date():
266
raise serializers.ValidationError(
267
"Start date cannot be in the past"
268
)
269
270
max_attendees = attrs.get('max_attendees')
271
if max_attendees and max_attendees < 1:
272
raise serializers.ValidationError(
273
"Maximum attendees must be at least 1"
274
)
275
276
return attrs
277
```
278
279
### Custom Validators
280
281
```python { .api }
282
from rest_framework import validators
283
284
def validate_positive(value: int) -> int:
285
"""Ensure value is positive."""
286
if value <= 0:
287
raise serializers.ValidationError("Value must be positive")
288
return value
289
290
class ProductSerializer(serializers.ModelSerializer[Product]):
291
price = serializers.DecimalField(
292
max_digits=10,
293
decimal_places=2,
294
validators=[validate_positive]
295
)
296
297
class Meta:
298
model = Product
299
fields = ['name', 'price', 'category', 'stock_quantity']
300
validators = [
301
validators.UniqueTogetherValidator(
302
queryset=Product.objects.all(),
303
fields=['name', 'category']
304
)
305
]
306
```
307
308
## Serialization Operations
309
310
### Basic Serialization
311
312
```python { .api }
313
# Serializing single object
314
book = Book.objects.get(id=1)
315
serializer = BookSerializer(book)
316
data = serializer.data # dict[str, Any]
317
318
# Serializing multiple objects
319
books = Book.objects.all()
320
serializer = BookSerializer(books, many=True)
321
data = serializer.data # list[dict[str, Any]]
322
323
# Including context
324
serializer = BookSerializer(book, context={'request': request})
325
data = serializer.data
326
```
327
328
### Deserialization and Validation
329
330
```python { .api }
331
# Creating new object
332
data = {'title': 'New Book', 'author_id': 1}
333
serializer = BookSerializer(data=data)
334
335
if serializer.is_valid():
336
book = serializer.save() # Returns Book instance
337
return Response(serializer.data, status=status.HTTP_201_CREATED)
338
else:
339
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
340
341
# Updating existing object
342
book = Book.objects.get(id=1)
343
data = {'title': 'Updated Title'}
344
serializer = BookSerializer(book, data=data, partial=True)
345
346
if serializer.is_valid():
347
updated_book = serializer.save()
348
return Response(serializer.data)
349
else:
350
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
351
```
352
353
### Advanced Serialization Patterns
354
355
```python { .api }
356
class BookListSerializer(serializers.ModelSerializer[Book]):
357
"""Minimal serializer for list views."""
358
author_name = serializers.CharField(source='author.full_name', read_only=True)
359
360
class Meta:
361
model = Book
362
fields = ['id', 'title', 'author_name', 'rating']
363
364
class BookDetailSerializer(serializers.ModelSerializer[Book]):
365
"""Full serializer for detail views."""
366
author = AuthorSerializer(read_only=True)
367
reviews = ReviewSerializer(many=True, read_only=True)
368
369
class Meta:
370
model = Book
371
fields = ['id', 'title', 'description', 'author', 'reviews', 'published_date']
372
373
# Dynamic serializer selection
374
def get_serializer_class(self) -> type[BaseSerializer]:
375
if self.action == 'list':
376
return BookListSerializer
377
return BookDetailSerializer
378
```
379
380
## Utility Functions
381
382
### Serializer Helpers
383
384
```python { .api }
385
def as_serializer_error(exc: Exception) -> dict[str, list[ErrorDetail]]:
386
"""Convert exception to serializer error format."""
387
...
388
389
def raise_errors_on_nested_writes(
390
method_name: str,
391
serializer: BaseSerializer,
392
validated_data: Any
393
) -> None:
394
"""Raise error if nested writes are attempted."""
395
...
396
```
397
398
### Constants
399
400
```python { .api }
401
LIST_SERIALIZER_KWARGS: Sequence[str]
402
LIST_SERIALIZER_KWARGS_REMOVE: Sequence[str]
403
ALL_FIELDS: str # Value: '__all__'
404
```
405
406
## Error Handling
407
408
### Validation Errors
409
410
```python { .api }
411
from rest_framework.exceptions import ValidationError
412
413
# Field-level validation error
414
def validate_age(self, value: int) -> int:
415
if value < 0 or value > 150:
416
raise ValidationError("Age must be between 0 and 150")
417
return value
418
419
# Object-level validation error
420
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
421
if attrs['password'] != attrs['confirm_password']:
422
raise ValidationError("Passwords don't match")
423
return attrs
424
```
425
426
### Error Response Format
427
428
```python { .api }
429
# Single field error
430
{
431
"username": ["This field is required."]
432
}
433
434
# Multiple field errors
435
{
436
"username": ["This field is required."],
437
"email": ["Enter a valid email address."]
438
}
439
440
# Non-field errors
441
{
442
"non_field_errors": ["Passwords don't match."]
443
}
444
```
445
446
## Performance Considerations
447
448
### Optimizing Queries
449
450
```python { .api }
451
class AuthorSerializer(serializers.ModelSerializer[Author]):
452
book_count = serializers.SerializerMethodField()
453
454
class Meta:
455
model = Author
456
fields = ['id', 'name', 'book_count']
457
458
def get_book_count(self, obj: Author) -> int:
459
# Use prefetch_related to avoid N+1 queries
460
return obj.books.count()
461
462
# In view:
463
queryset = Author.objects.prefetch_related('books')
464
```
465
466
### Field Selection
467
468
```python { .api }
469
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
470
"""Serializer that allows dynamic field selection."""
471
472
def __init__(self, *args: Any, **kwargs: Any) -> None:
473
fields = kwargs.pop('fields', None)
474
super().__init__(*args, **kwargs)
475
476
if fields is not None:
477
allowed = set(fields)
478
existing = set(self.fields)
479
for field_name in existing - allowed:
480
self.fields.pop(field_name)
481
```
482
483
This comprehensive serializer system provides type-safe data conversion, validation, and serialization for Django REST Framework applications, with full mypy support for catching type errors during development.