Return list of objects as dictionary with keys as

2019-04-06 09:24发布

问题:

Currently, I have a ListAPIView that returns a list of object dictionaries:

[
  { id: 1, ...},
  { id: 2, ...},
  ...
]

I would like to change this to be formatted as a dictionary with the id as the key:

{
  "1": { id: 1, ...},
  "2": { id: 2, ...},
  ...
}

How do I customize the output in this way using Django Rest Framework? Currently I am doing the re-formatting client side, but I would like to do it server side.

回答1:

I think you can implement the to_representation function in your Serializer.

class MySerializer(serializers.Serializer):
    id = serializers.ReadOnlyField()
    field1 = serializers.ReadOnlyField()
    field2 = serializers.ReadOnlyField()

    def to_representation(self, data):
        res = super(MySerializer, self).to_representation(data)
        return {res['id']: res}
        # or you can fetch the id by data directly
        # return {str(data.id): res}


回答2:

You can traverse each item and with a dict comprehension create your desired dictionary. For example:

>>> l = [{ "id": 1, "x": 4}, { "id": 2, "x": 3}]
>>> {v["id"]: v for v in l}
{1: {'x': 4, 'id': 1}, 2: {'x': 3, 'id': 2}}


回答3:

EDIT: current version available in a Github project and PYPI (pip install drf-keyed-list)

Here's a general-purpose class that is bi-directional (vs. the read-only implementation above):

class KeyedListSerializer(ListSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        meta = getattr(self.child, 'Meta', None)
        assert hasattr(meta, 'keyed_list_serializer_field'), \
            "Must provide a field name at keyed_list_serializer_field when using KeyedListSerializer"
        self._keyed_field = meta.keyed_list_serializer_field

    def to_internal_value(self, data):
        # syntax is py3 only
        data = [{**v, **{self._keyed_field: k}} for k, v in data.items()]
        return super().to_internal_value(data)

    def to_representation(self, data):
        response = super().to_representation(data)
        return {v.pop(self._keyed_field): v for v response}

For Py2, you need to make the super calls explicit and replace the indicated dictionary constructor. You use it by assigning it to your list_serializer_class and selecting a keyed_list_serializer_field (i.e. the field used as the dict key):

class MySerializer(ModelSerializer):

    class Meta:
        list_serializer_class = KeyedListSerializer
        keyed_list_serializer_field = 'id'

The keyed_list_serializer_field should contain unique values; the above implementation doesn't check.