“Not a valid string” in DRF Serializer Validation for ArrayField
Published on .
Recently we faced DRF Serializer validation raising “Not a valid string” errors on an ArrayField. Debugging it was difficult since the serializer implementation was not doing anything special.
class FooSerializer(serializers.ModelSerializer):
interests = ArrayField(child=serializers.CharField())
class Meta:
model = Foo
fields = ['interests']
The payload was multipart/form-data
encoded form data that looked like the following. Note the trailing []
on the field name! It was news to me, but this is a convention to indicate a field may have more than one value, according to a StackOverflow question. To HTML its just a name, but… Django doesn’t have any special handling for such a thing.
------WebKitFormBoundaryushtEp1zi8Tb6Pqe
Content-Disposition: form-data; name="interests[]"
a
------WebKitFormBoundaryushtEp1zi8Tb6Pqe
Content-Disposition: form-data; name="interests[]"
b
------WebKitFormBoundaryushtEp1zi8Tb6Pqe--
So since Django has no special handling, then interests[]
was being passed to the serializer, but the serializer was looking for interests
! So we added an interests attribute with a copy of the data via data['interests'] = data.getlist('interests[]')
which is what introduced the Not a valid string
validation error, since data
is actually a complex Django data structure called a QueryDict
that behaves somewhat like a dict()
but is not a dict.
Using the set dict key notation, the docs say: “Sets the given key to [value] (a list whose single element is value).” which explains the issue, since the serializer is actually getting passed a list of lists, but it is expecting a list of strings, so validation fails!
The fix was data.setlist('interests', data.getlist('interests[]')
The simplest way to debug is adding a breakpoint like below and having an easy way to call the API with the payload, so that it is easy to see what’s getting passed and what the state of the program is (e.g. running data
from pdb
very quickly reveals the problem: 'interests': [['Sales', 'Marketing']]
where it is obvious that the first item in the list is not a string (its a list!)
if not serializer.is_valid():
breakpoint()