Serializers
Serializers are the core component of DRF, handling data conversion in two directions: converting model objects to JSON (serialization), and validating and converting JSON submitted by clients back to model objects (deserialization). DRF provides two types of serializers: Serializer, where fields are declared manually, and ModelSerializer, which maps automatically to a model.
Serializer Basics
Defining a Serializer
All serializers inherit from rest_framework.serializers.Serializer. Field declarations are similar to Django Forms — each field corresponds to a data type:
from rest_framework import serializers
class StudentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100)
age = serializers.IntegerField(min_value=0, max_value=150)
sex = serializers.BooleanField(default=True)
description = serializers.CharField(required=False, allow_blank=True)Common Field Types
| Field Type | Description |
|---|---|
CharField | String; supports max_length, min_length |
IntegerField | Integer; supports max_value, min_value |
FloatField | Float |
DecimalField | Fixed-point number; requires max_digits, decimal_places |
BooleanField | Boolean |
DateField | Date (YYYY-MM-DD) |
DateTimeField | Date and time |
EmailField | Email; automatic format validation |
URLField | URL; automatic format validation |
UUIDField | UUID |
ChoiceField | Enum; requires choices |
ListField | List; requires child field child |
DictField | Dictionary; requires child field child |
SerializerMethodField | Read-only computed field; value returned by a get_<field_name> method |
Common Field Parameters
| Parameter | Default | Description |
|---|---|---|
read_only | False | Used for serialization output only; ignored during deserialization |
write_only | False | Used for deserialization input only; hidden in serialization output |
required | True | Whether the field must be provided during deserialization |
default | — | Default value when not provided |
allow_null | False | Whether null/None is allowed |
allow_blank | False | Whether an empty string is allowed (CharField only) |
validators | [] | Additional validator functions |
error_messages | — | Custom error message dictionary |
label | — | Field name displayed in the visual interface |
help_text | — | Help text displayed in the visual interface |
Serialization (Model → JSON)
Serializing a Single Object
from students.models import Student
from .serializers import StudentSerializer
student = Student.objects.get(pk=1)
serializer = StudentSerializer(instance=student)
print(serializer.data)
# {'id': 1, 'name': 'Zhang San', 'age': 20, 'sex': True, 'description': '...'}Returning from a view:
from django.views import View
from django.http import JsonResponse
from students.models import Student
from .serializers import StudentSerializer
class StudentView(View):
def get(self, request, pk):
student = Student.objects.get(pk=pk)
serializer = StudentSerializer(instance=student)
return JsonResponse(serializer.data)Serializing Multiple Objects
When the data source is a QuerySet, pass many=True:
students = Student.objects.all()
serializer = StudentSerializer(instance=students, many=True)
# serializer.data is a list
return JsonResponse(serializer.data, safe=False)SerializerMethodField Example
Used to add computed fields to the output:
class StudentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField()
age = serializers.IntegerField()
# Computed field: whether the student is an adult based on age
is_adult = serializers.SerializerMethodField()
def get_is_adult(self, obj):
return obj.age >= 18Deserialization (JSON → Model)
Data Validation
Deserialization flow: receive client data → instantiate the serializer (pass data) → call is_valid() → access validated_data.
data = {
"name": "Li Si",
"age": 22,
"sex": True,
"description": "Test user",
}
serializer = StudentSerializer(data=data)
if serializer.is_valid():
print(serializer.validated_data) # OrderedDict containing validated data
else:
print(serializer.errors) # Dictionary containing field errorsTo raise an HTTP 400 directly on validation failure:
serializer.is_valid(raise_exception=True)Custom Validation (Hooks)
DRF provides three ways to extend validation, executed in priority order from highest to lowest:
1. Field-level hook validate_<field_name>
class StudentSerializer(serializers.Serializer):
name = serializers.CharField()
def validate_name(self, value):
if value == "admin":
raise serializers.ValidationError("Username cannot be 'admin'")
return value # Must return the validated value2. Object-level hook validate
Used for cross-field combined validation:
def validate(self, data):
if data.get("age", 0) < 18 and data.get("sex") is False:
raise serializers.ValidationError("Female minors are not allowed to register")
return data # Must return data3. validators functions
Attach independent validator functions to fields for easy reuse:
def check_age(value):
if value == 0:
raise serializers.ValidationError("Age cannot be 0")
return value
class StudentSerializer(serializers.Serializer):
age = serializers.IntegerField(validators=[check_age])Saving Data (create / update)
When serializer.save() is called, DRF dispatches automatically to create() or update() depending on whether instance was passed. These two methods must be implemented in the serializer:
class StudentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(required=True, max_length=100)
age = serializers.IntegerField(min_value=0, max_value=150)
sex = serializers.BooleanField(default=True)
description = serializers.CharField(required=False, allow_blank=True)
def create(self, validated_data):
return Student.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get("name", instance.name)
instance.age = validated_data.get("age", instance.age)
instance.sex = validated_data.get("sex", instance.sex)
instance.description = validated_data.get("description", instance.description)
instance.save()
return instanceUsing it in a view:
# Create (no instance passed)
serializer = StudentSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
student = serializer.save() # Calls create()
# Update (instance passed)
student = Student.objects.get(pk=pk)
serializer = StudentSerializer(instance=student, data=request.data)
serializer.is_valid(raise_exception=True)
student = serializer.save() # Calls update()
# Partial update (PATCH)
serializer = StudentSerializer(instance=student, data=request.data, partial=True)save() also accepts extra keyword arguments that are merged into validated_data:
serializer.save(created_by=request.user)ModelSerializer
ModelSerializer inherits from Serializer and automatically generates fields from the model class. It also includes built-in create() and update() implementations, greatly reducing boilerplate code.
Basic Definition
from rest_framework import serializers
from .models import Student
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__" # Include all model fieldsMeta Configuration Options
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model = Student
# Option 1: list the required fields
fields = ["id", "name", "age", "sex", "description"]
# Option 2: exclude specific fields (mutually exclusive with fields; cannot use both)
# exclude = ["description"]
# Read-only field list (equivalent to setting read_only=True on each field)
read_only_fields = ("id",)
# Add or override field options
extra_kwargs = {
"sex": {"write_only": True},
"description": {"required": False, "allow_blank": True},
}Adding Extra Fields
ModelSerializer also supports declaring additional fields (which must be listed in fields):
class StudentModelSerializer(serializers.ModelSerializer):
full_label = serializers.SerializerMethodField()
class Meta:
model = Student
fields = ["id", "name", "age", "sex", "full_label"]
def get_full_label(self, obj):
return f"{obj.name} (Class {obj.class_null})"Passing Extra Context
Sometimes you need to access request, the current user, or other information inside a serializer. Pass it from the view via context and access it inside the serializer using self.context:
# View layer
serializer = StudentModelSerializer(
instance=student,
context={"request": request, "project_id": 42}
)
# Inside the serializer
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
def validate(self, data):
request = self.context.get("request")
if not request.user.is_staff:
raise serializers.ValidationError("You do not have permission to perform this action")
return dataGenericAPIView and its subclasses, the get_serializer() method automatically injects request, view, and format into context, so you do not need to pass them manually.Serializer vs ModelSerializer
| Comparison | Serializer | ModelSerializer |
|---|---|---|
| Field declaration | Declared manually, one by one | Auto-generated from model |
create/update | Must be implemented manually | Built-in default implementation |
| Use cases | Non-model data, highly customized APIs | Standard CRUD, closely aligned with model fields |
| Code volume | More | Less; rapid development |
For most standard CRUD interfaces, choose ModelSerializer. When serializing data from multiple models or needing complex field calculations, choose Serializer.