Created
March 7, 2025 16:06
-
-
Save salomvary/b8c49ab456dff7d12812542934a85150 to your computer and use it in GitHub Desktop.
Efficient Django Rest Framework (DRF) Serializers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# How to write performant serializers for Django REST Framework. | |
# | |
# Focusing on serialization, but mostly also applicable to deserialization. | |
# Remember: do not optimize prematurely, always measure performance first. | |
# But if you are planning to serialize large and complicated data structures, | |
# it is very likely that you will not want to use ModelSerializers. | |
from functools import lru_cache | |
from django.db import models | |
from rest_framework import serializers | |
class SomeModel(models.Model): | |
field1 = models.CharField() | |
field2 = models.CharField() | |
# Do NOT use ModelSerializers for non-trivial serialization. | |
# They are inherently slow. Acceptable for a dozen of fields, | |
# or lists of a dozen elements. | |
class SomeModelSerializer(serializers.ModelSerializer): | |
class Meta: | |
model = SomeModel | |
fields = ["field1", "field2"] | |
# Serialize with it like this: | |
SomeModelSerializer().to_representation(SomeModel.objects.first()) | |
SomeModelSerializer(many=True).to_representation(SomeModel.objects.all()) | |
# DO use plain serializers. | |
# They are faster, and the code-overhead of explicitly defining | |
# fields is not huge. | |
class SomePlainSerializer(serializers.Serializer): | |
field1 = serializers.CharField() | |
field2 = serializers.CharField() | |
# Same serialization patterns work here: | |
SomePlainSerializer().to_representation(SomeModel.objects.first()) | |
SomePlainSerializer(many=True).to_representation(SomeModel.objects.all()) | |
# Also note that serializer instances can be reused: | |
serializer = SomePlainSerializer() | |
serializer.to_representation(SomeModel.objects.first()) | |
serializer.to_representation(SomeModel.objects.last()) | |
class ChildSerializer(serializers.Serializer): | |
field2 = serializers.CharField() | |
# DO use child serializers as a serializer field | |
class ParentSerializer(serializers.Serializer): | |
field1 = serializers.CharField() | |
child_field = ChildSerializer(source="field2") | |
# You can also pass the entire parent instance to the child serializer | |
class ParentSerializer(serializers.Serializer): | |
field1 = serializers.CharField() | |
child_field = ChildSerializer(source="*") | |
# Do NOT instantiate serializers from a SerializerMethodField | |
class ParentSerializer(serializers.Serializer): | |
field1 = serializers.CharField() | |
child_field = serializers.SerializerMethodField() | |
def get_child_field(self, instance): | |
# Instantiating serializers is expensive, and if ParentSerializer is used | |
# in a list, the performance impact will be significant. | |
return ChildSerializer(context=self.context).to_representation(instance.field2) | |
# If SerializerMethodField is unavoidable, | |
# DO cache the child serializer on the parent serializer | |
class ParentSerializer(serializers.Serializer): | |
field1 = serializers.CharField() | |
child_field = serializers.SerializerMethodField() | |
def get_child_field(self, instance): | |
# Instantiating serializers is expensive, and if ParentSerializer is used | |
# in a list, the performance impact will be significant. | |
return self._get_child_serializer().to_representation(instance.field2) | |
@lru_cache | |
def _get_child_serializer(self): | |
# Assuming that ParentSerializer does not modify the context. | |
return ChildSerializer(context=self.context) | |
# Bonus: DO consider not passing in models to serializers at all. | |
# This saves the cost of instantiating a model instance, which might | |
# or might not matter for your use case. | |
# Also beware that nested relations selected with .values("parent__child") | |
# will not be exposed as nested objects but as flat fields, which requires | |
# writing serializers differently, or preprocessing the return value | |
# from the queryset before passing it to the serializer. | |
SomePlainSerializer().to_representation( | |
SomeModel.objects.values("field1", "field2").first() | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment