Django Rest Framework custom ListSerializer only returning dictionary keys, not values

Viewed 2814

I am using a custom ListSerializer to reformat a JSON response and it generates the proper format when I log from inside the serializer, but when it is returned to the ViewSet, the response becomes a list of the dictionary keys rather than including the entire dictionary with both keys and values.

I have made a simplified example of the problem with hard-coded JSON to illustrate what I believe to be the crux of the issue.

views.py

class ElementViewSet(viewsets.ViewSet):

    ...

    @detail_route(methods=['get'])
    def elements(self, request, pk=None):
        elements = ElementFilterSet(params)
        serializer = serializers.ElementSerializer(elements, many=True)
        return Response(serializer.data)

serializers.py

class ElementListSerializer(serializers.ListSerializer):

    def to_representation(self, obj):
        result = {"home": {"label_color": "#123456","label_text": "young"},"speak": { "label_color": "","label_text": "Hello"}}
        return result


class ElementSerializer(serializers.ModelSerializer):
    class Meta:
        model = Element
        list_serializer_class = ElementListSerializer

    def to_representation(self, obj):
        result = super(ElementSerializer, self).to_representation(obj)
        return result

The response I get is a list of dictionary keys:

[
    "speak",
    "home"
]

rather than what I want, which is the entire dictionary (in this case, simply the hard-coded JSON result):

{
   "home": {
       "label_color": "#123456",
       "label_text": "young"
   },
   "speak": {
       "label_color": "",
       "label_text": "Hello"
   }
}

I am new to customizing serializers and using list_serializer_class; I likely simply don't understand how they work, but this behavior does seem unexpected to me.

2 Answers

According to docs definition: The ListSerializer class provides the behavior for serializing and validating multiple objects at once. You don't typically need ListSerializers, if the data you pass could be represented as serializer data. Suggested solution is to use nested serializers, if you dont get the key values from a model instance:

class ElementListSerializer(serializers.BaseSerializer):

    def to_representation(self, obj):
        return {
            'home': {"label_color": "#123456",
                     "label_text": "young"},
            'speak': {
                    "label_color": "",
                    "label_text": "Hello"}
        }

class ElementSerializer(serializers.ModelSerializer):

    element_list = ElementListSerializer() 

    class Meta:
        model = Element

    def create(self, validated_data):
        data = validated_data.pop('element_list')
        return data

The problem is in property data on ListSerializer which returns ReturnList instead of ReturnDict.

To fix your code, you have to change data property:

from rest_framework import serializers


class ElementListSerializer(serializers.ListSerializer):

    def to_representation(self, obj):
        result = {"home": {"label_color": "#123456","label_text": "young"},"speak": { "label_color": "","label_text": "Hello"}}
        return result

    @property
    def data(self):
        ret = serializers.BaseSerializer.data.fget(self)
        return serializers.ReturnDict(ret, serializer=self)


class ElementSerializer(serializers.ModelSerializer):
    class Meta:
        model = Element
        list_serializer_class = ElementListSerializer

    def to_representation(self, obj):
        result = super(ElementSerializer, self).to_representation(obj)
        return result

You can also create a more generic solution. It will automatically convert list of dicts with same structure to one dict where keys will be from specified field from child's dict.

class ListToDictSerializer(serializers.ListSerializer):
    def to_representation(self, data):
        return {
            item[self.child.Meta.dict_serializer_key]: self.child.to_representation(item)
            for item in data
        }

    @property
    def data(self):
        ret = drf_serializers.BaseSerializer.data.fget(self)
        return serializers.ReturnDict(ret, serializer=self)


class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Model
        list_serializer_class = ListToDictSerializer
        dict_serializer_key = 'id'
Related