Search code examples
pythondjangodjango-rest-frameworkdjango-serializer

Is there a way to exclude fields in nested serializers with a dictfield?


I am trying to serialize a dictionary in the structure of:

dict = {'outer_dict': {'inner_dict1': {'key1': 'value1', 'key2': 'value2'}, 
                       'inner_dict2': {'key3': 'value3', 'key4': 'value4'}}}

But want to exclude key1 from my Response(dictserializer.data)

I have tried to accomplish it like this

class InnerDictSerializer(serializers.Serializer):
    key1 = serializers.CharField()
    key2 = serializers.CharField()

    def to_representation(self, instance):
        data = super().to_representation(instance)
        excluded_fields = ['key1']
        for field in excluded_fields:
            data.pop(field, None)
        return data

class DictSerializer(serializers.Serializer):
    dict = serializers.DictField(child=InnerDictSerializer())

Which successfully eats the dictionary data, but the to_representation of the InnerDictSerializer is not called and thus in the dictserializer.data the key1 information is given. After digging through the django source code I realized it is due to the kwarg 'data=..' of InnerDictSerializer not being given, but I have gained no information about how I could intercept the proccess and give it to the serializer or achieve the result in another way.

While going through the sourcecode I am puzzled about when the to_representation method of a field is called in any case?

In addition the default of django seems to be to make an ordereddict of it. For easier testing and due to not needing the order I would like to customize it to make a defaultdict of the values. Thus I tried it with a custom serializer;

class UnorderedDictSerializer(serializers.Serializer):
    def to_representation(self, instance):
        return defaultdict(self.to_representation, instance)

But this doesn't work on the inner dictionaries to as the to_representation isn't called. I realize with a model_serializer I could use 'exclude', but how does this work for a basic serializer?

Update edit: This actually works. In the real code tested I just had a combination of Errors interfering and short circuiting the functionality in a pretty hard to see way.


Solution

  • First, to answer:

    While going through the sourcecode I am puzzled about when the to_representation method of a field is called in any case?

    Basically, it runs when you call .data which is a property of BaseSerializer. The Serializer class (which inherits from BaseSerializer) has a .to_representation method already implemented, there is where each field is transformed. That is the method used when we call super().to_representation(instance)!

    Now, to answer your main question, there are two approaches. The first one is using .to_representation method:

    serializers.py

    class InnerDictOneSerializer(serializers.Serializer):
        key1 = serializers.CharField()
        key2 = serializers.CharField()
    
        def to_representation(self, instance):
            representation = super().to_representation(instance)
            representation.pop('key1')
            return representation
    
    class InnerDictTwoSerializer(serializers.Serializer):
        key3 = serializers.CharField()
        key4 = serializers.CharField()
    
    class OuterDictSerializer(serializers.Serializer):
        inner_dict1 = InnerDictOneSerializer()
        inner_dict2 = InnerDictTwoSerializer()
    
    class Wrapper(serializers.Serializer):
        outer_dict = OuterDictSerializer()
    

    And a second one by using the extra argument write_only that would make the field readable only when creating data:

    class InnerDictOneSerializer(serializers.Serializer):
        key1 = serializers.CharField(write_only=True)
        key2 = serializers.CharField()
    
    ...