Computer scienceBackendDjangoAPI

Serializers in DRF

8 minutes read

In general, serialization is a way to convert data from source to target data structure so the end client can comprehend it.

Deserialization is the opposite process of converting data to a format that will be understood by a source from the data sent by the end client.

For example, imagine a conversation between two people who speak two very different languages — Bengali and Russian. How can they possibly communicate? They can get help from an interpreter who understands both Bengali and Russian. When Bengali says something, and the statement is translated to Russian — it's serialization. And when the Russian says something and the reply is translated to Bengali — it's deserialization. The interpreter, in this case, is the serializer.

When communicating over an API, we serialize internal data into a commonly used language like JSON or XML. And then, we also deserialize data from JSON or XML into our internal data structures. Nowadays, however, thanks to its convenience, everyone opts for JSON in API communication.

Django Rest Framework serializes Python objects into JSON data to send it back to the client. In most cases, it is based on Javascript. DRF serializers are very similar to Django Form and Django ModelForm classes.

Setting up

First, let's create a new Django project called magazine:

django-admin startproject magazine

Now, create a new app named stories:

python manage.py startapp stories

Add put rest_frameworkand stories into INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'rest_framework',
    'stories',
]

Our project is ready for the first serializer class! Let's create a new file in the stories folder. Name it serializers.py. We will include our serializer here.

The structure of our project now looks like this:

magazine
├── stories
│   ├── urls.py
│   ├── models.py
│   └── serializers.py
└── magarize
    ├── urls.py
    └── settings.py

Creating serializer class

Let's go to Django Shell:

python manage.py shell

And define a Python class named Artice. We will serialize objects of this class in the following example:

from datetime import datetime


class Article():
        def __init__(self, title, content, author, created_at=datetime.now):
        self.title = title
        self.content = content
        self.author = author
        self.created_at = created_at

    def __str__(self):
        return f'{self.title} - {self.author} - {self.created_at}'

Now, we'll create an article object using the Article class:

forest_article = Article(
    'The Forest',
    'The forest is a place where you can find many different animals.',
    'John Doe')

We'll then import serializers from rest_framework and create a new ArticleSerializer class that extends the Serializer class of the serializers module:

from rest_framework import serializers


class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField(max_length=100)
    author = serializers.CharField(max_length=100)
    created_at = serializers.DateTimeField()

Serialization

We can also use ArticleSerializer to serialize the Article object. Another option is to use a list of Article objects adding the parameter many=True:

serializer = ArticleSerializer(forest_article)
serializer.data
# {'title': 'The Forest', 
# 'content': 'The forest is a place where you can find many different animals.',
# 'author': 'John Doe', 'created_at': '2021-12-08T18:25:43.968064Z'}

This is how we convert it to the Python native datatype. Now, let's convert it to JSON:

from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
json
# b'{"title":"The Forest",
# "content":"The forest is a place where you can find many different animals.",
# "author":"John Doe","created_at":"2021-12-08T18:25:43.968064Z"}'

Now, we have created a JSON from our article object. We can send it back to the client application.

Deserialization

We have learned how to serialize our objects into JSON. But what if we want to do the opposite? When the client application sends JSON data representing an article, we must convert it into an article object.

Let's start deserializing! First, we parse the Python native datatype:

from rest_framework.parsers import JSONParser
import io

stream = io.BytesIO(json)
data = JSONParser().parse(stream)

Now, feed the native datatype into the serializer. It will return an ordered dictionary. After this, pass the unpacked validated data to the Article class to create an article:

serializer = ArticleSerializer(data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', 'The Forest'), 
# ('content', 'The forest is a place where you can find many different animals.'), 
# ('author', 'John Doe'), 
# ('created_at', datetime.datetime(2021, 12, 8, 18, 25, 43, 968064,
# tzinfo=backports.zoneinfo.ZoneInfo(key='UTC')))])

forest_article = Article(**serializer.validated_data)
forest_article.title
# 'The Forest'
forest_article.created_at
# datetime.datetime(2021, 12, 8, 18, 25, 43, 968064, tzinfo=<UTC>)

We can create a serializer for any Python class to serialize and deserialize Python objects. Let's now learn about some fields and methods with the Django Rest Framework.

Serializer fields & methods

Serializers in Django provide many fields to serialize and deserialize data of different types. These fields are similar to the Django model fields. Commonly used fields include CharField, TextField, EmailField, IntegerField, DecimalField, DateTimeField, and so on.

title = serializers.CharField(max_length=100)

Django Rest Framework serializers also provide many helper methods. For example, the is_valid() method validates data and returns True or False depending on what is passed to the serializer:

if serializer.is_valid():
    # do something

We can override these methods to change the behavior of our serializers. We can define the create and update methods to perform these actions with our serializer.

ModelSerialziers

The ModelSerializers creates fields specified in the Django Model class automatically when we create a serializer class by extending ModelSerializer. So we don't need to repeat the same code. Moreover, it also implements action methods such as create(), update(), and others. Let's create a model serializer class. First, create the Article model:

# models.py
class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)

Now, create a serializer for the Article model extending the ModelSerializer class:

# serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = ('title', 'content', 'author', 'created_at')

Here, in the Meta class, we need to specify two fields — the Model name and the fields in the ArticleSerializer. We can also use exclude to specify unnecessary fields.

Changing serializer behavior

We can override the create(), update(), and save() methods to change the behavior of the serializer action methods. For example, if we want to change data before saving it to the database, we can override the save() method. We can also override the create and update methods:

# serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        """ do something if you want before creating an entry in the database/model instance """
        return Article.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """ do something upon updating the data in the database or model instance """
        instance.title= validated_data.get('title', instance.title) + "updated"
        instance.content = validated_data.get('content', instance.content)
        instance.author= validated_data.get('author', instance.author)
        instance.save()
        return instance

    def save(self):
        """ Send an email upon saving """
        title= self.validated_data['title']
        message = self.validated_data['content']
        send_email(from='[email protected]', message=message)

    class Meta:
        model = Article
        fields = ('title', 'content', 'author', 'created_at')

You can change the behavior of the serializers by overriding the ModelSerializer based classes or defining these methods in the Serializer-based classes. We can also override the validate() method to change the validation behavior:

def validate(self, data):
    """
    Validate if the time of creation is valid or not.
    """
    today = timezone.now()
    if data['created'] > today:
        raise serializers.ValidationError("You cannot validate in future.")
    return data

So, now we know many ways to convert data to JSON format. But in addition to all the benefits of JSON, you must be aware of its disadvantages, including limited support for comments and the requirement for a strict syntactic structure. Let's now talk about another possible format for data serialization.

YAML renderer

YAML (YAML Ain't Markup Language) is a convenient and readable data format widely applied in various areas, including configuration settings, structured data, and API serialization.

DRF makes it easy to use YAML as a data serialization format. This is especially useful if you need to exchange data between different systems, where YAML is the preferred exchange format.

To work with YAML in DRF, you can use a special serializer, YAMLSerializer, which allows you to convert data from and to the YAML format. To start using YAMLSerializer, you need to install the PyYAML library using the command:

pip install pyyaml

Then you need to use the command to use YAML with DRF:

pip install djangorestframework-yaml

Finally, you need to add rest_framework_yaml.parsers.YAMLParser and rest_framework_yaml.renderers.YAMLRenderer to DRF settings:

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework_yaml.parsers.YAMLParser',
        # ...
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework_yaml.renderers.YAMLRenderer',
        # ...
    ],
}

Now you can create serializer classes that inherit from rest_framework_yaml.serializers.YAMLSerializer:

from rest_framework_yaml.serializers import YAMLSerializer

class ArticleSerializer(YAMLSerializer):
    class Meta:
        model = Article
        fields = ('title', 'content', 'author', 'created_at')

That's all! Now, you can work with YAML data in your Django project. You can read more about how to do this on the official website.

YAML is a data serialization language that has several advantages over JSON. One of the main benefits of YAML is that it is more human-readable and understandable. It uses indentation, lists, and dictionaries to structure data. Additionally, it supports various data types such as strings, numbers, booleans, dates, times, nulls, and even executable code. Another advantage of YAML is that it requires fewer characters and tokens to represent data than JSON, saving memory and time.

However, these benefits come with some drawbacks. One downside of YAML is that it is sensitive to whitespace and indentation. This makes it easier to introduce errors into the code. Additionally, YAML may be less secure than JSON because it allows arbitrary code to be executed or referenced to external resources. Finally, YAML may be less performant than JSON because it requires a more complex analyzer.

Conclusion

Django Rest Framework's serializers are an excellent tool for handling request data and generating response data for APIs based on Django data structures. While we've explored effective data serialization and deserialization, custom solutions may be worth exploring in some cases, especially with straightforward requirements and one-way data transformation.

However, DRF's serializer classes, particularly ModelSerializer, can speed up development. Additionally, we've seen how integrating serialization with efficient formats like Msgpack can further enhance API performance and resource utilization. We'll delve into advanced DRF serializer functionalities in upcoming discussions, including custom fields, validation, and method overrides. Whether you choose DRF's built-in tools or a tailored approach, these features empower precise API data management.

5 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo