Hello! Welcome back. In our last lesson, we successfully installed and configured Django REST Framework (DRF) in our project, setting the stage for building a powerful API. You now have a project ready to serve data to any client.
Today, we dive into the single most important concept in DRF: serialization. This is the process that allows your Django application to speak the universal language of web APIs—JSON. Our learning outcome is to create a Serializer to convert simple data types to JSON.
We'll start with the foundational serializers.Serializer class, which is perfect for understanding the core mechanics. You'll learn how to convert simple Python objects (not tied to the database) into JSON and, just as importantly, how to perform the reverse process: validating and converting incoming JSON data back into Python data structures.
This lesson builds the conceptual groundwork that will make working with database models in our next lesson feel intuitive and straightforward.
What is Serialization?
At its heart, an API's job is to transfer data. However, a web client (like a browser or mobile app) can't directly understand a complex Python object, such as an instance of a class you've written. Serialization is the bridge that solves this problem.
Serialization is the process of converting complex data structures (like Python objects) into a format (like JSON) that can be easily stored or transmitted over a network. Deserialization is the reverse process: converting the JSON data back into a complex data structure after it's been received.

For a concise explanation, the official DRF documentation is the best place to start.
The DRF documentation provides a clear and authoritative definition of what serializers do. Pay close attention to the analogy with Django's Form classes, which you might find familiar.
Read the introductory section titled 'Serializers'. Focus on understanding their dual role: converting objects to JSON (serialization) and validating incoming data (deserialization).
Creating Your First Serializer
Let's get practical. To understand serializers in their purest form, we'll work with a simple Python class that is not a Django model. This allows us to focus entirely on the serialization logic itself.
Imagine we are building an API for a reporting tool and need to represent a simple report object.
# A simple Python object, not a Django model
from datetime import datetime
class Report:
def __init__(self, title, author, word_count, generated_at=None):
self.title = title
self.author = author
self.word_count = word_count
self.generated_at = generated_at or datetime.now()
Now, let's create a serializer to handle these Report objects. Notice how its structure mirrors both the Python class and a standard Django Form. You'll define fields that correspond to the attributes you want to serialize.
# In a new file in your app, e.g., 'serializers.py'
from rest_framework import serializers
class ReportSerializer(serializers.Serializer):
title = serializers.CharField(max_length=150)
author = serializers.CharField(max_length=100)
word_count = serializers.IntegerField()
generated_at = serializers.DateTimeField(read_only=True) # `read_only=True` means this field is used for output, not input.
The video below provides an excellent hands-on demonstration of this process using the Python shell. It will help you see these concepts in action.
Introduction to Serializers - DRF Tutorial - Part 4
Watch this segment from Vitor Freitas's tutorial to see how a basic serializer is defined and used with a simple object.
Watch from 02:44 to 06:13. The instructor uses a mock object, which serves the same purpose as our Report class. Notice how the serializer fields (CharField, IntegerField) are declared and how the serializer is instantiated with an object to produce a dictionary.
Serialization: From Python Object to JSON
With our Report class and ReportSerializer defined, let's perform the serialization. This is a two-step process:
- Translate the object into a native Python datatype (a dictionary).
- Render that dictionary into JSON.
You can test this easily in the Django shell (python manage.py shell).
# In the Django shell
from your_app.serializers import ReportSerializer
from your_app.reports import Report # Assuming you saved the class in reports.py
from rest_framework.renderers import JSONRenderer
# 1. Create an instance of our Python object
report_instance = Report(title="Q3 Financials", author="Jane Doe", word_count=2500)
# 2. Instantiate the serializer with the object
serializer = ReportSerializer(report_instance)
# 3. Access the .data property to get the dictionary
print(serializer.data)
# Output: {'title': 'Q3 Financials', 'author': 'Jane Doe', 'word_count': 2500, 'generated_at': '2023-10-27T10:30:00.123456Z'}
# 4. Render the dictionary to a JSON byte string
json_output = JSONRenderer().render(serializer.data)
print(json_output)
# Output: b'{"title":"Q3 Financials","author":"Jane Doe","word_count":2500,"generated_at":"2023-10-27T10:30:00.123456Z"}'
This flow is also clearly laid out in the official documentation.
This section of the documentation provides a clear, text-based example of the entire serialization process, including the final rendering to JSON.
Review the sections 'Declaring Serializers' and 'Serializing objects'. This reinforces the code we just wrote and shows the same pattern applied to a Comment object.
Deserialization and Validation
Handling incoming data is just as important. Deserialization involves validating raw input data (like a JSON payload from a POST request) and converting it into a validated dictionary.
The key steps are:
- Pass the incoming data to the serializer using the
data=keyword argument. - Call the
is_valid()method to run validation checks. - If valid, access the clean data from
validated_data. - If invalid, access the error details from
errors.
Let's see this in the shell:
# In the Django shell
from your_app.serializers import ReportSerializer
import io
from rest_framework.parsers import JSONParser
# 1. Simulate an incoming JSON payload
incoming_json = b'{"title": "Annual Review", "author": "John Smith", "word_count": 5000}'
stream = io.BytesIO(incoming_json)
data = JSONParser().parse(stream)
# 2. Instantiate the serializer with the incoming data
serializer = ReportSerializer(data=data)
# 3. Validate the data
is_valid = serializer.is_valid()
print(f"Is the data valid? {is_valid}")
# Output: Is the data valid? True
# If valid, access the cleaned data
if is_valid:
print(serializer.validated_data)
# Output: OrderedDict([('title', 'Annual Review'), ('author', 'John Smith'), ('word_count', 5000)])
# --- Example of invalid data ---
invalid_json = b'{"title": "", "word_count": -50}' # Missing author, blank title, negative word count
stream = io.BytesIO(invalid_json)
data = JSONParser().parse(stream)
serializer = ReportSerializer(data=data)
print(f"\nIs the invalid data valid? {serializer.is_valid()}")
# Output: Is the invalid data valid? False
# Inspect the errors
print(serializer.errors)
# Output: {'author': ['This field is required.'], 'title': ['This field may not be blank.']}
# Note: IntegerField validation for negative numbers would require a custom validator.
The next video segment is an excellent deep dive into the validation process, including how to add custom validation logic.
Introduction to Serializers - DRF Tutorial - Part 4
Validation is a critical function of serializers. Watch this segment to see how to use is_valid() and how to implement custom validation rules for your data.
Watch from 12:41 to 19:39. Pay close attention to: Calling is_valid() and inspecting the .errors dictionary. Raising a ValidationError inside a custom validate_<field_name> method. Using the general validate method for cross-field validation.
Test your understanding!
You are creating a serializer for a Product object which has name (CharField), price (DecimalField), and sku (CharField).
# The input data from an API request
data = {
"name": "Super Widget",
"price": "99.99"
}
You create a ProductSerializer(data=data) and call serializer.is_valid(). It returns False, and serializer.errors is {'sku': ['This field is required.']}.
What are the two key differences between the serializer.data and serializer.validated_data attributes in this context?
Show answer
- Availability:
serializer.datais available after serialization (when passing an object instance), but for deserialization, you should only accessserializer.validated_dataafter successfully callingis_valid(). Attempting to accessvalidated_databeforeis_valid()will raise an error. - Content:
serializer.datacontains the primitive, rendered output.serializer.validated_datacontains the validated incoming data, converted to the appropriate Python types (e.g., the string"99.99"would be converted to aDecimalobject). It represents data that has passed all validation rules and is safe to use in your application logic.
Beyond Simple Fields: SerializerMethodField
Serializers aren't limited to one-to-one attribute mapping. You can also include computed values. The SerializerMethodField is perfect for this. It allows you to run a Python method to generate the value for a field.
Introduction to Serializers - DRF Tutorial - Part 4
Let's explore a more dynamic feature. This video shows how to use SerializerMethodField to include a calculated value (a person's age) in the serialized output.
Watch from 08:44 to 12:41. Notice the pattern: you define a SerializerMethodField and then create a corresponding get_<field_name> method on the serializer class. This is a powerful technique for adding derived data to your API responses.
Conclusion
Excellent work! You've just mastered the most fundamental building block of Django REST Framework. Understanding how a basic Serializer works from the ground up is crucial for building robust and predictable APIs.
Key Takeaways:
- Serialization converts Python objects into simple datatypes (like dictionaries) for rendering into JSON.
- Deserialization validates and converts incoming data into Python datatypes.
- The base
serializers.Serializerclass gives you explicit, fine-grained control, making it perfect for non-model data or complex validation scenarios. - The validation flow is key: instantiate with
data=..., callis_valid(), and then usevalidated_dataor inspecterrors. - You can add computed values to your output using
SerializerMethodField.
Preview of the Next Lesson:
Manually defining every field in a serializer can feel repetitive, especially when your data comes directly from a Django model. What if the serializer could inspect your model and automatically generate the fields? In our next lesson, we will explore ModelSerializer, a powerful tool that does exactly that, significantly reducing boilerplate code when working with your database.