Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Django REST Framework serializers: reject coercion outside the wire contract

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A Django REST Framework serializer validates received fields and exposes accepted values through validated_data after successful validation.

Download Python source kit

Operation contract

The receipt serializer declares an exact field set, a six-character ASCII receipt identifier and an integer minor-unit amount. Its custom field rejects text and boolean amounts instead of coercing them into integers. The serializer checks the complete record before the caller reads its accepted data. Serialization itself performs no storage publication.

Failure and ownership boundary

Some built-in fields intentionally accept coercible representations; that may be correct for another protocol but not this one. Unknown fields and duplicate JSON keys need separate policies, and parsed dictionaries cannot recover duplicates already collapsed by a parser. Identity and object permissions also remain separate. Python type conversion: parsing success is not field validity and Django REST Framework object permissions: check the retrieved record, not only login define those boundaries.

Tested environment

Dependency check: this program was executed on CPython 3.14.6 with Django==5.2.17, djangorestframework==3.18.1. Install these versions in a separate virtual environment. The download includes the recorded environment snapshot; no third-party package is part of the website runtime.

Working program

python
from django.conf import settings
settings.configure(INSTALLED_APPS=[], USE_I18N=False)
import django
django.setup()
from rest_framework import serializers

class MinorAmount(serializers.Field):
    def to_internal_value(self, value):
        if type(value) is not int or not 0 <= value <= 1000000:
            raise serializers.ValidationError("exact bounded integer required")
        return value
    def to_representation(self, value):
        return value

class ReceiptInput(serializers.Serializer):
    receipt_id = serializers.RegexField(r"\AR-[0-9]{4}\Z", max_length=6, trim_whitespace=False)
    amount = MinorAmount()
    def to_internal_value(self, data):
        if not isinstance(data, dict) or set(data) != {"receipt_id", "amount"}:
            raise serializers.ValidationError({"non_field_errors": ["exact field set required"]})
        return super().to_internal_value(data)

accepted = ReceiptInput(data={"receipt_id": "R-0041", "amount": 125})
accepted.is_valid(raise_exception=True)
print(dict(accepted.validated_data))
for amount in (True, "125", -1):
    candidate = ReceiptInput(data={"receipt_id": "R-0041", "amount": amount})
    print("accepted:", candidate.is_valid())
extra = ReceiptInput(data={"receipt_id": "R-0041", "amount": 125, "owner_id": 41})
print("extra accepted:", extra.is_valid())

Output

Output
{'receipt_id': 'R-0041', 'amount': 125}
accepted: False
accepted: False
accepted: False
extra accepted: False

Costs and limits

Validation scans the selected fields and retains accepted/error state. Bounds on fields do not replace a pre-parse request byte limit. A valid serializer does not imply an indexed query, transaction or permitted record mutation.

Common Mistakes

  • Read validated_data only after successful validation.
  • Accepting a schema does not prove that the caller owns a receipt.

Connected lessons

Python type conversion: parsing success is not field validity, Python JSON validation: reject duplicate members and non-integer amounts, Django REST Framework object permissions: check the retrieved record, not only login.

Check the next state boundary

Django REST Framework creation ownership: assign the owner from the principal.

python
drf-serializers
Storage details