I've noticed that the Serializer isn't really strict when it comes to rejecting input with unknown fields:
In [1]: from rest_framework import serializers
In [2]: class TestSerializer(serializers.Serializer):
...: foo = serializers.CharField()
...:
In [3]: s = TestSerializer(data=dict(foo='foo', bar='bar'))
In [4]: s.is_valid()
Out[4]: True
Is there a way to configure the Serializer
to return a validation error about bar
being unexpected in this situation?
This definitely works:
class TestSerializer(serializers.Serializer):
foo = serializers.CharField()
def validate(self, attrs):
unknown = set(self.initial_data) - set(self.fields)
if unknown:
raise ValidationError("Unknown field(s): {}".format(", ".join(unknown)))
return attrs
Nested and list serializers
This will not work if you use a such a serializer as a field in another serializer. In which case, the child serializer won't have access to initial data and you will get an exception.
Same with a ListSerializer
(or many=True
) as the list serializer's child serializer won't get the individual initial_data
items (there is a github ticket for this).
In that case the slightly less clean solution which works all both cases is:
from rest_framework.fields import empty
from rest_framework.settings import api_settings
class TestSerializer(serializers.Serializer):
foo = serializers.CharField()
def run_validation(self, data=empty):
if data is not empty:
unknown = set(data) - set(self.fields)
if unknown:
errors = ["Unknown field: {}".format(f) for f in unknown]
raise serializers.ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: errors,
})
return super(TestSerializer, self).run_validation(data)
s.data
does not contain bar
so what is the use case where it matters?
After looking at the docs I didn't see a native solution. You could override .validate()
to do a check and raise ValidationErrors
that way. I didn't test this with when partial=True
so you will want to check that if you're using it.
class TestSerializer(serializers.Serializer):
foo = serializers.CharField()
def validate(self, attrs):
has_unknown_fields = set(attrs.keys()) - set(self.fields.keys())
if has_unknown_fields:
raise serializers.ValidationError("dont send extra fields")
return attrs
With django REST framework v. 3.3.0 it will be:
class _FieldSetValidatingSerializer(serializers.Serializer):
def is_valid(self, raise_exception=False):
super().is_valid(False)
fields_keys = set(self.fields.keys())
input_keys = set(self.initial_data.keys())
additional_fields = input_keys - fields_keys
if bool(additional_fields):
self._errors['fields'] = ['Additional fields not allowed: {}.'.format(list(additional_fields))]
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)