Serialization is the process of transforming data into a format that can be stored or transmitted and then reconstructing it. It's used all the time when developing applications or storing data in databases, in memory, or converting it into files.
I recently helped two junior developers at Labcodes understand serializers, and I thought it would be good to share my approach with Opensource.com readers.
Suppose you're creating software for an e-commerce site and you have an Order that registers the purchase of a single product, by someone, at a price, on a date:
class Order:
def __init__(self, product, customer, price, date):
self.product = product
self.customer = customer
self.price = price
self.date = date
Now, imagine you want to store and retrieve order data from a key-value database. Luckily, its interface accepts and return dictionaries, so you need to convert your object into a dictionary:
def serialize_order(order):
return {
'product': order.product,
'customer': order.customer,
'price': order.price,
'date': order.date
}
And if you want to get some data from that database, you can get the dict data and turn that into your Order object:
def deserialize_order(order_data):
return Order(
product=order_data['product'],
customer=order_data['customer'],
price=order_data['price'],
date=order_data['date'],
)
This is pretty straightforward to do with simple data, but when you need to deal with complex objects made of complex attributes, this approach doesn't scale well. You also need to handle the validation of different types of fields, and that's a lot of work to do manually.
That's where frameworks' serializers are handy. They allow you to create serializers with little boilerplates that will work for your complex cases.
Django comes with a serialization module that allows you to "translate" Models into other formats:
from django.core import serializers
serializers.serialize('json', Order.objects.all())
It covers the most-used cases for web applications, such as JSON, YAML, and XML. But you can also use third-party serializers or create your own. You just need to register it in your settings.py file:
# settings.py
SERIALIZATION_MODULES = {
'my_format': appname.serializers.MyFormatSerializer,
}
To create your own MyFormatSerializer
, you need to implement the .serialize()
method and accept a queryset and extra options as params:
class MyFormatSerializer:
def serialize(self, queryset, **options):
# serious serialization happening
Now you can serialize your queryset to your new format:
from django.core import serializers
serializers.serialize('my_format', Order.objects.all())
You can use the options parameters to define the behavior of your serializer. For example, if you want to define that you're going to work with nested serialization when dealing with ForeignKeys
or you just want that data to return its primary keys, you can pass a flat=True
param as an option and deal with that within your method:
class MyFormatSerializer:
def serializer(self, queryset, **options):
if options.get('flat', False):
# don't recursively serialize relationships
# recursively serialize relationships
One way to use Django serialization is with the loaddata
and dumpdata
management commands.
DRF serializers
In the Django community, the Django REST framework (DRF) offers the best-known serializers. Although you can use Django's serializers to build the JSON you'll respond to in your API, the one from the REST framework comes with nice features that help you deal with and easily validate complex data.
In the Order example, you could create a serializer like this:
from restframework import serializers
class OrderSerializer(serializers.Serializer):
product = serializers.CharField(max_length=255)
customer = serializers.CharField(max_lenght=255)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
date = serializers.DateField()
And easily serialize its data:
order = Order('pen', 'renato', 10.50, date.today())
serializer = OrderSerializer(order)
serializer.data
# {'product': 'pen', 'customer': 'renato', 'price': '10.50', 'date': '2020-08-16'}
To be able to return an instance from data, you need to implement two methods—create and update:
from rest_framework import serializers
class OrderSerializer(serializers.Serializer):
product = serializers.CharField(max_length=255)
customer = serializers.CharField(max_length=255)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
date = serializers.DateField()
def create(self, validated_data):
# perform order creation
return order
def update(self, instance, validated_data):
# perform instance update
return instance
After that, you can create or update instances by calling is_valid()
to validate the data and save()
to create or update an instance:
serializer = OrderSerializer(**data)
## to validate data, mandatory before calling save
serializer.is_valid()
serializer.save()
Model serializers
When serializing data, you often need to do it from a database, therefore, from your models. A ModelSerializer, like a ModelForm, provides an API to create serializers from your models. Suppose you have an Order model:
from django.db import models
class Order(models.Model):
product = models.CharField(max_length=255)
customer = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
date = models.DateField()
You can create a serializer for it like this:
from rest_framework import serializers
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = '__all__'
Django automatically includes all model fields in the serializer and creates the create
and update
methods.
Using serializers in class-based views (CBVs)
Like Forms with Django's CBVs, serializers integrate well with DRFs. You can set the serializer_class
attribute so that the serializer will be available to the view:
from rest_framework import generics
class OrderListCreateAPIView(generics.ListCreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
You can also define the get_serializer_class()
method:
from rest_framework import generics
class OrderListCreateAPIView(generics.ListCreateAPIView):
queryset = Order.objects.all()
def get_serializer_class(self):
if is_free_order():
return FreeOrderSerializer
return OrderSerializer
There are other methods in CBVs that interact with serializers. For example, get_serializer() returns an already-instantiated serializer, while get_serializer_context() returns the arguments you'll pass to the serializer when creating its instance. For views that create or update data, there are create
and update
that validate the data with the is_valid
method to be saved, and perform_create and perform_update that call the serializer's save method.
Learn more
For other resources, see my friend André Ericson's Classy Django REST Framework website. It is a Classy Class-Based Views REST Framework version that gives you an in-depth inspection of the classes that compose DRF. Of course, the official documentation is an awesome resource.
Comments are closed.