~/TechPurAI
~/tutorials/django-rest-framework/serializers
beginner·part 2 of 22·2 min read

Serializers: turning a Django model into JSON

Updated Aug 16, 2026Python · Django

A serializer is DRF's translator between a Django model instance and JSON, in both directions: model → JSON for a response, and JSON → validated Python data for a request. This part writes one by hand, field by field — the shortcut that generates this automatically arrives in part 3, but seeing the manual version first is what makes the shortcut make sense.

A plain Serializer

python
# tasks/serializers.py
from rest_framework import serializers

class TaskSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(max_length=200)
    description = serializers.CharField(required=False, allow_blank=True)
    completed = serializers.BooleanField(default=False)
    due_date = serializers.DateField(required=False, allow_null=True)

Every field mirrors a model field, but declared separately — a serializer field is about the JSON shape and validation rules, not storage. read_only=True on id means it appears in output but is ignored on input, since a client never gets to choose a task's ID.

Serializing: model to JSON

python
from rest_framework.renderers import JSONRenderer
from tasks.models import Task

task = Task.objects.first()
serializer = TaskSerializer(task)
serializer.data
# {'id': 1, 'title': 'Write the API', 'description': '', 'completed': False, 'due_date': None}

JSONRenderer().render(serializer.data)
# b'{"id":1,"title":"Write the API","description":"","completed":false,"due_date":null}'

Passing a model instance as the first argument puts the serializer in "output" mode — .data is an OrderedDict of plain Python values, not JSON text yet. JSONRenderer is the separate step that turns it into actual bytes; a real view (starting part 4) handles that automatically, but seeing it as two distinct steps here is what makes the "serializer versus renderer" split make sense.

Deserializing: JSON to validated data

python
data = {"title": "Ship part 2", "completed": False}
serializer = TaskSerializer(data=data)
serializer.is_valid()          # True
serializer.validated_data
# OrderedDict([('title', 'Ship part 2'), ('completed', False)])

Passing data= instead puts the serializer in "input" mode. .is_valid() runs every field's validation — required fields present, types correct, max_length respected — and only after it returns True is .validated_data safe to read, the same is_valid() → cleaned_data/validated_data shape Django's own Form uses.

What .is_valid() catches

python
bad = TaskSerializer(data={"description": "no title"})
bad.is_valid()          # False
bad.errors
# {'title': [ErrorDetail(string='This field is required.', code='required')]}

title has no default and isn't required=False, so DRF requires it — the same field-declaration rules that build the browsable API's HTML form (part 18) are exactly what produces this error message.

Common mistake

Reading serializer.data on a serializer constructed with data= before checking .is_valid(). On an input-mode serializer, .validated_data is what's safe after validation — .data in that mode can raise or return something unexpected, since it's designed for the output direction.

Next: ModelSerializer — the same result as this hand-written class, generated from the model in a few lines instead of one per field.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous1. Installing Django REST Framework and setting up the projectnext →3. ModelSerializer: the shortcut for model-backed APIs