DRF serializer parsing comma-delimited string into a list field
Is there a way of modifying how DRF serializers parse incoming request payload?
I'm trying to let clients send a comma-delimited list as query parameter but receive it inside the serializer as a list instead but DRF keeps complaining. Right now I'm manually intercepting the request in the view and doing the parsing that field manually before passing it to the serializer which doesn't seem elegant to me.
What I'm doing right now
class ExampleSerializer(...):
list_field = serialzers.ListField(child=serializers.Integerfield(...))
# more fields
def view(request):
payload = request.GET
payload["list_field"] = str(payload.get("list_field", "")).split(",")
serializer = ExampleSerializer(data=payload)
What I'd prefer (using same serializer as above)
def view(request):
serializer = ExampleSerializer(data=request.GET)
The ListField
will work with json, or with multi-value query strings or form bodies (as below). It does not parse comma separated strings.
This will work:
GET /path/?list_field=1&list_field=2&list_field=3
What you need is a custom field which implements your parsing logic: accept a string and split it using a separator (,
, or :
, etc), and then validate it using the child field.
There is no builtin field which works this way, but there is a great example GIST here which you can copy or reference when writing your own field. I have included some snippets from the gist, but as its not mine I don't feel comfortable copying the whole thing.
# https://gist.github.com/imomaliev/77fdfd0ab5f1b324f4e496768534737e
class CharacterSeparatedField(serializers.ListField):
def __init__(self, *args, **kwargs):
self.separator = kwargs.pop("separator", ",")
super().__init__(*args, **kwargs)
def to_internal_value(self, data):
data = data.split(self.separator)
return super().to_internal_value(data)
# continues ...
class TestCharacterSeparatedManyField:
def test_field_from_native_should_return_list_for_given_str(self):
field = CharacterSeparatedField(child=serializers.CharField())
assert field.to_internal_value("a,b,c") == ["a", "b", "c"]
You can also write a custom validate_{fieldname}
function to modify the value. This at least keeps it in the serializer. A proper Field is better if possible, though, but this is a common pattern for one-off validation/transformations like this.
class ExampleSerializer(Serializer):
list_field = CharField()
def validate_list_field(self, value):
arr = value.split(",")
arr = [int(x) for x in arr if x.isdigit()]
if len(arr) == 0:
raise ValidationError("Supply at least 1 value.")
return arr