2

I have an API that accepts JSON. The JSON data has a key called type which should be a choice field and have a value among 5 specified types. Say A,B,C,D & E.

There are different fields that JSON can have depending upon whether the value is A,B,C, D or E. How do I go about validating the other fields that may or may not be there?

1 Answer 1

3

There are two parts to this question:

  • How do I go about validating multiple fields?
  • How do I only validate a subset of fields for some input?

I will answer these two questions individually.

How do I go about validating multiple fields?

Django REST Framework allows you to validate multiple fields by overriding the validate method on the serializer. This is done last, after all other validators have run, and there is a detailed explanation in the documentation about it.

def validate(self, attrs):
    if "type" in attrs:
        the_type = attrs["type"]

        if the_type == "A":
            # Check if "a_field" was actually passed
            if "a_field" not in attrs:
                raise ValidationError({"a_field": self.error_messages["required"]})

            # Check that it's "great"
            if attrs["a_field"] != "great":
                raise Va    class DemoSerializer(serializers.Serializer):
    type = serializers.ChoiceField(choices=types)
    a_field = serializers.CharField(required=False)

    def to_representation(self, instance):
        optional_fields = {
            "B": ["a_field"],
        }

        data = super(DemoSerializer, self).to_representation(instance)

        instance_type = data["type"]

        if instance_type in optional_fields:
            remove_fields = optional_fields[instance_type]

            for field in remove_fields:
                if field in data:
                    data.pop(field)

        return datalidationError({"a_field": self.error_messages["not_great"]})

    return attrs

This will only validate a_field if type is "A".

How do I only validate a subset of fields for some input?

This is the tricky part, as by default Django REST Framework will not pass data through the serializer that has a key not in the fields list. So if, in our example, you didn't add a_field to the list, an error would always be triggered that the field is required, even if the data was passed in. This means you will need to add all of the optional fields to your serializer and mark them as required=False.

class DemoSerializer(serializers.Serializer):
    type = serializers.ChoiceField(choices=types)
    a_field = serializers.CharField(required=False)

This has the unfortunate side effect of making all of these optional fields show when the data is serialized using the serializer, but that is something you can fix by overriding to_representation. You will need to know what fields to remove ahead of time.

class DemoSerializer(serializers.Serializer):
    type = serializers.ChoiceField(choices=types)
    a_field = serializers.CharField(required=False)

    def to_representation(self, instance):
        optional_fields = {
            "B": ["a_field"],
        }

        data = super(DemoSerializer, self).to_representation(instance)

        instance_type = data["type"]

        if instance_type in optional_fields:
            remove_fields = optional_fields[instance_type]

            for field in remove_fields:
                if field in data:
                    data.pop(field)

        return data

This should allow you to remove a list of fields based on the type that is returned.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for such a detailed explanation. My first approach was to make different serializers per type and let them inherit common fields from a parent type. Seems like, it's not possible to go this way. It has to be a single serializer with all fields pre-declared but where required is set to false.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.