Remove null fields from Django Rest Framework resp

2019-02-06 18:47发布

问题:

I have developed an API using django-rest-framework. I am using ModelSerializer to return data of a model.

models.py

class MetaTags(models.Model):
    title = models.CharField(_('Title'), max_length=255, blank=True, null=True)
    name = models.CharField(_('Name'), max_length=255, blank=True, null=True)

serializer.py

class MetaTagsSerializer(serializers.ModelSerializer):
    class Meta:
        model = MetaTags

response

{
    "meta": {
        "title": null,
        "name": "XYZ"
    }
}

Ideally in an API response any value which is not present should not be sent in the response. When the title is null I want the response to be:

{
    "meta": {
        "name": "XYZ"
    }
}

回答1:

You could try overriding the to_native function:

class MetaTagsSerializer(serializers.ModelSerializer):
    class Meta:
        model = MetaTags

    def to_native(self, obj):
        """
        Serialize objects -> primitives.
        """
        ret = self._dict_class()
        ret.fields = self._dict_class()

        for field_name, field in self.fields.items():
            if field.read_only and obj is None:
                continue
            field.initialize(parent=self, field_name=field_name)
            key = self.get_field_key(field_name)
            value = field.field_to_native(obj, field_name)

            # Continue if value is None so that it does not get serialized.
            if value is None:
                continue

            method = getattr(self, 'transform_%s' % field_name, None)
            if callable(method):
                value = method(obj, value)
            if not getattr(field, 'write_only', False):
                ret[key] = value
            ret.fields[key] = self.augment_field(field, field_name, key, value)

        return ret

I basically copied the base to_native function from serializers.BaseSerializer and added a check for the value.

UPDATE: As for DRF 3.0, to_native() was renamed to to_representation() and its implementation was changed a little. Here's the code for DRF 3.0 which ignores null and empty string values:

def to_representation(self, instance):
    """
    Object instance -> Dict of primitive datatypes.
    """
    ret = OrderedDict()
    fields = self._readable_fields

    for field in fields:
        try:
            attribute = field.get_attribute(instance)
        except SkipField:
            continue

        # KEY IS HERE:
        if attribute in [None, '']:
            continue

        # We skip `to_representation` for `None` values so that fields do
        # not have to explicitly deal with that case.
        #
        # For related fields with `use_pk_only_optimization` we need to
        # resolve the pk value.
        check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
        if check_for_none is None:
            ret[field.field_name] = None
        else:
            ret[field.field_name] = field.to_representation(attribute)

    return ret


回答2:

The answer from CubeRZ didn't work for me, using DRF 3.0.5. I think the method to_native has been removed and is now replaced by to_representation, defined in Serializer instead of BaseSerializer.

I used the class below with DRF 3.0.5, which is a copy of the method from Serializer with a slight modification.

from collections import OrderedDict

from rest_framework import serializers
from rest_framework.fields import SkipField

class NonNullSerializer(serializers.ModelSerializer):

    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        ret = OrderedDict()
        fields = [field for field in self.fields.values() if not field.write_only]

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            if attribute is not None:
                represenation = field.to_representation(attribute)
                if represenation is None:
                    # Do not seralize empty objects
                    continue
                if isinstance(represenation, list) and not represenation:
                   # Do not serialize empty lists
                   continue
                ret[field.field_name] = represenation

        return ret

EDIT incorporated code from comments



回答3:

I faced a similar problem and solved it as follows:

class MetaTagsSerializer(serializers.ModelSerializer):
    class Meta:
        model = MetaTags

    def to_representation(self, instance):
        ret = super(MetaTagsSerializer, self).to_representation(instance)
        # Here we filter the null values and creates a new dictionary
        # We use OrderedDict like in original method
        ret = OrderedDict(list(filter(lambda x: x[1], ret.items())))
        return ret

Or if you want to filter only empty fields you can replace the lambda function by the following:

lambda x: x[1] is not None


回答4:

I found this solution to be the simplest.

from collections import OrderedDict
from rest_framework import serializers

class NonNullModelSerializer(serializers.ModelSerializer):
    def to_representation(self, instance):
        result = super(NonNullModelSerializer, self).to_representation(instance)
        return OrderedDict([(key, result[key]) for key in result if result[key] is not None])