# Django REST Framework Serializers em Profundidade: Validação, Aninhamento e N+1 > Domine os serializers DRF com técnicas avançadas de validação, padrões de serializers aninhados e estratégias de otimização de consultas N+1. Exemplos de código prontos para produção incluídos. - Published: 2026-07-12 - Updated: 2026-07-12 - Author: SharpSkill - Reading time: 11 min --- Os serializers do Django REST Framework lidam com a tarefa complexa de converter querysets e instâncias de modelos em respostas JSON—e validar dados de entrada antes que cheguem ao banco de dados. Embora o uso básico de serializers pareça simples, aplicações em produção exigem domínio do pipeline de validação, relacionamentos aninhados e otimização de consultas. > **Regra de Performance dos Serializers DRF** > > Cada `SerializerMethodField` ou serializer aninhado acessando objetos relacionados sem `select_related`/`prefetch_related` dispara consultas adicionais ao banco de dados. Uma lista de 100 objetos com 3 relacionamentos significa 301 consultas em vez de 4. ## Entendendo o Pipeline de Validação dos Serializers DRF Os serializers DRF executam validação em uma ordem específica: deserialização no nível do campo, validadores no nível do campo, e então validação no nível do objeto via `validate()`. Este pipeline determina quando e como interceptar transformações de dados. A sequência de validação começa com `to_internal_value()`, que deserializa tipos de dados primitivos e executa validadores de campos. Somente após todos os campos passarem pela validação individual é que `validate()` executa para verificações entre campos. ```python # serializers.py from rest_framework import serializers from django.utils import timezone from .models import Event class EventSerializer(serializers.ModelSerializer): start_date = serializers.DateTimeField() end_date = serializers.DateTimeField() class Meta: model = Event fields = ['id', 'title', 'start_date', 'end_date', 'location'] def validate_start_date(self, value): # Validação no nível do campo executa primeiro if value < timezone.now(): raise serializers.ValidationError("Start date cannot be in the past.") return value def validate(self, attrs): # Validação no nível do objeto executa após todos os campos start = attrs.get('start_date') end = attrs.get('end_date') if start and end and end <= start: raise serializers.ValidationError({ 'end_date': "End date must be after start date." }) return attrs ``` Essa separação permite controle granular: capturar valores de campo obviamente inválidos cedo, e então validar regras de negócio que abrangem múltiplos campos. ## Validadores Personalizados e Lógica de Validação Reutilizável DRF suporta três padrões de validadores: métodos no nível do campo, classes de validadores independentes, e o argumento `validators`. Validadores independentes promovem reutilização entre serializers e mantêm responsabilidade única. ```python # validators.py from rest_framework import serializers import re class SlugFormatValidator: """Valida formato de slug compatível com URL.""" def __init__(self, allow_unicode=False): self.allow_unicode = allow_unicode self.pattern = r'^[\w-]+$' if allow_unicode else r'^[a-z0-9-]+$' def __call__(self, value): if not re.match(self.pattern, value): raise serializers.ValidationError( "Slug must contain only lowercase letters, numbers, and hyphens." ) class UniqueForUserValidator: """Valida unicidade limitada ao usuário atual.""" requires_context = True def __init__(self, queryset, field): self.queryset = queryset self.field = field def __call__(self, value, serializer_field): request = serializer_field.context.get('request') if not request or not request.user.is_authenticated: return queryset = self.queryset.filter( user=request.user, **{self.field: value} ) # Excluir instância atual durante atualizações instance = serializer_field.parent.instance if instance: queryset = queryset.exclude(pk=instance.pk) if queryset.exists(): raise serializers.ValidationError( f"You already have an item with this {self.field}." ) ``` Aplicar validadores de forma declarativa mantém as classes de serializers focadas na estrutura em vez da implementação de validação. ```python # serializers.py from .validators import SlugFormatValidator, UniqueForUserValidator from .models import Project class ProjectSerializer(serializers.ModelSerializer): slug = serializers.CharField( max_length=100, validators=[ SlugFormatValidator(), UniqueForUserValidator(Project.objects.all(), 'slug') ] ) class Meta: model = Project fields = ['id', 'name', 'slug', 'description'] ``` ## Serializers Aninhados: Relacionamentos Graváveis Implementados Corretamente Serializers aninhados permitem ler e escrever objetos relacionados em uma única requisição. O desafio está em lidar com criação, atualizações e manter integridade referencial através dos relacionamentos. Para operações de leitura, serializers aninhados funcionam automaticamente. Operações de escrita requerem sobrescritas explícitas dos métodos `create()` e `update()` já que DRF não consegue inferir como lidar com dados aninhados. ```python # models.py from django.db import models class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField(unique=True) class Book(models.Model): title = models.CharField(max_length=300) isbn = models.CharField(max_length=13, unique=True) author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books') class Chapter(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='chapters') number = models.PositiveIntegerField() title = models.CharField(max_length=200) ``` O serializer lida com criação e atualização de capítulos aninhados dentro de um livro: ```python # serializers.py from rest_framework import serializers from django.db import transaction from .models import Author, Book, Chapter class ChapterSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) # Permitir ID para atualizações class Meta: model = Chapter fields = ['id', 'number', 'title'] class BookSerializer(serializers.ModelSerializer): chapters = ChapterSerializer(many=True) author_name = serializers.CharField(source='author.name', read_only=True) class Meta: model = Book fields = ['id', 'title', 'isbn', 'author', 'author_name', 'chapters'] @transaction.atomic def create(self, validated_data): chapters_data = validated_data.pop('chapters', []) book = Book.objects.create(**validated_data) Chapter.objects.bulk_create([ Chapter(book=book, **chapter_data) for chapter_data in chapters_data ]) return book @transaction.atomic def update(self, instance, validated_data): chapters_data = validated_data.pop('chapters', []) # Atualizar campos do livro for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() # Rastrear capítulos existentes para detecção de exclusão existing_ids = set(instance.chapters.values_list('id', flat=True)) updated_ids = set() for chapter_data in chapters_data: chapter_id = chapter_data.pop('id', None) if chapter_id and chapter_id in existing_ids: # Atualizar capítulo existente Chapter.objects.filter(id=chapter_id).update(**chapter_data) updated_ids.add(chapter_id) else: # Criar novo capítulo Chapter.objects.create(book=instance, **chapter_data) # Excluir capítulos não incluídos na requisição instance.chapters.filter(id__in=existing_ids - updated_ids).delete() return instance ``` O decorator `@transaction.atomic` garante que todas as operações aninhadas tenham sucesso ou falhem juntas—crítico para consistência de dados em APIs de produção. > **Armadilha de Atualização em Serializers Aninhados** > > Sem tratamento explícito de ID em serializers aninhados, cada requisição de atualização cria novos objetos relacionados em vez de modificar os existentes. Sempre incluir `id = serializers.IntegerField(required=False)` para objetos aninhados atualizáveis. ## Resolvendo o Problema de Consultas N+1 em Serializers DRF Consultas N+1 ocorrem ao serializar listas com objetos relacionados. Cada item na lista dispara consultas separadas para seus relacionamentos, devastando os tempos de resposta da API. Os [padrões de otimização de consultas Django ORM](/blog/django/django-orm-optimizing-queries) se aplicam diretamente ao DRF. Considere uma view retornando 50 livros com seus autores e capítulos: ```python # views.py - PROBLEMÁTICO: consultas N+1 from rest_framework import generics from .models import Book from .serializers import BookSerializer class BookListView(generics.ListAPIView): queryset = Book.objects.all() # 1 consulta para livros serializer_class = BookSerializer # +50 consultas para autores, +50 para capítulos = 101 total ``` A solução requer `select_related` para chaves estrangeiras e `prefetch_related` para relações inversas: ```python # views.py - OTIMIZADO: 3 consultas no total class BookListView(generics.ListAPIView): queryset = Book.objects.select_related('author').prefetch_related('chapters') serializer_class = BookSerializer ``` Para serializers complexos com lógica condicional, sobrescrever `get_queryset()` para corresponder aos requisitos do serializer: ```python # views.py from rest_framework import viewsets from django.db.models import Prefetch, Count from .models import Author from .serializers import AuthorDetailSerializer class AuthorViewSet(viewsets.ModelViewSet): serializer_class = AuthorDetailSerializer def get_queryset(self): return Author.objects.prefetch_related( Prefetch( 'books', queryset=Book.objects.select_related('publisher').annotate( chapter_count=Count('chapters') ).order_by('-publication_date') ) ) ``` ## Otimização de Performance do SerializerMethodField `SerializerMethodField` executa código Python para cada instância serializada. Realizar consultas ao banco de dados dentro desses métodos cria problemas N+1 ocultos que não aparecem nos logs de consultas padrão. ```python # serializers.py - PROBLEMÁTICO class AuthorSerializer(serializers.ModelSerializer): total_sales = serializers.SerializerMethodField() class Meta: model = Author fields = ['id', 'name', 'total_sales'] def get_total_sales(self, obj): # Consulta executada para CADA autor na lista return obj.books.aggregate(total=Sum('sales'))['total'] or 0 ``` A solução move a agregação para o nível do queryset: ```python # views.py from django.db.models import Sum class AuthorListView(generics.ListAPIView): queryset = Author.objects.annotate( total_sales=Sum('books__sales') ) serializer_class = AuthorSerializer ``` ```python # serializers.py - OTIMIZADO class AuthorSerializer(serializers.ModelSerializer): total_sales = serializers.IntegerField(read_only=True) # Da anotação class Meta: model = Author fields = ['id', 'name', 'total_sales'] ``` O campo anotado se torna um campo de serializer regular, eliminando consultas por instância completamente. ## Seleção Dinâmica de Campos com Contexto do Serializer APIs de produção frequentemente precisam de flexibilidade de campos—clientes mobile querem payloads mínimos enquanto dashboards de administração requerem todos os dados. Serializers dinâmicos adaptam a saída baseada no contexto da requisição. ```python # serializers.py class DynamicFieldsMixin: """Permite seleção de campos via parâmetro de consulta ?fields=id,name,email.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) request = self.context.get('request') if not request: return fields_param = request.query_params.get('fields') if fields_param: requested = set(fields_param.split(',')) existing = set(self.fields.keys()) # Remover campos não solicitados for field_name in existing - requested: self.fields.pop(field_name) class UserSerializer(DynamicFieldsMixin, serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email', 'first_name', 'last_name', 'date_joined'] ``` Requisições para `/api/users/?fields=id,username` retornam apenas esses campos, reduzindo o tamanho do payload e potencialmente permitindo otimização adicional de consultas baseada nos campos selecionados. > **Disponibilidade do Contexto do Serializer** > > O contexto só é preenchido quando o serializer é instanciado com uma requisição. Instanciação direta como `UserSerializer(data=payload)` tem contexto vazio—sempre passar `context={'request': request}` em ViewSets ou Views. ## Monitoramento de Performance e Análise de Consultas Identificar problemas de consultas induzidos por serializers requer visibilidade nas operações de banco de dados. [Django Debug Toolbar](https://django-debug-toolbar.readthedocs.io/) e `django-silk` fornecem análise de consultas por requisição durante o desenvolvimento. Para monitoramento em produção, registrar consultas lentas e rastrear tempo de serialização: ```python # middleware.py import time import logging from django.db import connection, reset_queries from django.conf import settings logger = logging.getLogger('api.performance') class QueryCountMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): reset_queries() start = time.perf_counter() response = self.get_response(request) duration = time.perf_counter() - start query_count = len(connection.queries) if query_count > settings.QUERY_COUNT_WARNING_THRESHOLD: logger.warning( 'High query count: %d queries in %.2fs for %s %s', query_count, duration, request.method, request.path ) return response ``` Definir `QUERY_COUNT_WARNING_THRESHOLD` baseado na complexidade da API—endpoints que retornam listas tipicamente precisam de 3-5 consultas independente do tamanho da página. Para uma [preparação completa para entrevistas Django REST Framework](/technologies/django/interview-questions/django-rest-framework), entender esses padrões de serializers distingue engenheiros seniores daqueles que conhecem apenas o uso básico. ## Conclusão - **A ordem de validação importa**: validadores no nível do campo executam antes de `validate()`, permitindo falha antecipada para dados obviamente inválidos - **Extrair validadores reutilizáveis**: classes de validadores independentes com `requires_context = True` acessam dados da requisição enquanto permanecem testáveis - **Escritas aninhadas precisam de tratamento explícito**: sobrescrever `create()` e `update()` com `@transaction.atomic` para integridade de dados - **Corresponder queryset ao serializer**: cada serializer aninhado e `SerializerMethodField` acessando relacionamentos requer `select_related`/`prefetch_related` correspondente - **Anotar em vez de calcular**: mover cálculos de `SerializerMethodField` para anotações de queryset para eliminar consultas por instância - **Monitorar contagem de consultas**: APIs de produção devem rastrear consultas ao banco de dados por requisição para detectar regressões N+1 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/django/django-rest-framework-serializers-deep-dive