In my application there's a model that stores a configuration in one of its fields. The field is defined as JSONField. I have a strict structure that defines what content of this field should look like, but I'm struggling to find a way how to serialize it to validate data in API requests.
The solution that works for now, but does not validate what's inside config_field, is blindly accept anything that complies with being a json object:
A simplified version of my model:
class MyModel(models.Model):
config_field = JSONField(...)
...
For the sake of this question here's a simplified version of the data structure stored in config_field:
{"some_config_int": 42, "some_config_vars": [{"id": 1}, {"id": 2}]}
And here's a simplified version of my serializer:
class MyModelSerializer(serializers.ModelSerializer):
config_field = serializers.JSONField(required=False)
class Meta:
model = MyModel
fields = ('config_field', ...)
What I'd like to achieve though is to have a serializer for the nested representation (reference to DRF documentation) of what's inside config_field. What I've tried so far (but does not work):
class ConfigVarsSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)
class ConfigFieldsSerializer(serializers.Serializer):
some_config_int = serializers.IntegerField(required=True)
some_config_vars = serializers.ListField(child=ConfigVarsSerializer,required=True)
class MyModelSerializer(serializers.ModelSerializer):
config_field = ConfigFieldsSerializer(required=False)
class Meta:
model = MyModel
fields = ('config_field', ...)
This way it will be optional to POST/PUT an object with a configuration, but if config_field is in the body of the request, the entire nested object should be provided.
After trying several possible solutions I want to point out 2 simplest and most importantly those that do not require overriding create method for neither MyModelSerializer nor inner serializers:
config_field in MyModelSerializervalidate method for entire object being serialized by MyModelSerializerSerializers representing inner content of config_field would be the same for both solutions:
class ConfigVarsSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)
class ConfigFieldsSerializer(serializers.Serializer):
some_config_int = serializers.IntegerField(required=True)
some_config_vars = serializers.ConfigVarsSerializer(required=True, many=True)
Note that
some_config_varsstores list of objects, that's whymany=True.
Override field validation method for config_field in MyModelSerializer. In case of given example the final code of serializer would be:
class MyModelSerializer(serializers.ModelSerializer):
config_field = JSONField(required=False)
class Meta:
model = MyModel
fields = ('config_field', ...)
def validate_config_field(self, value):
serializer = ConfigFieldsSerializer(data=value)
serializer.is_valid(raise_exception=True)
return value
This approach first validates config_field using default JSONFieldSerializer and raises exception if content isn't a valid JSON object.
If JSONFieldSerializer raises no exception validate_custom_fields is called and it passes content of field into ConfigFieldsSerializer and validates all content for itself and all nested serializers.
Override validate method for entire object being serialized by MyModelSerializer. In case of given example the final code of serializer would be:
class MyModelSerializer(serializers.ModelSerializer):
config_field = JSONField(required=False)
class Meta:
model = MyModel
fields = ('config_field', ...)
def validate(self, attrs):
config_field = attrs.get('config_field')
if config_field:
serializer = ConfigFieldsSerializer(data=config_field)
serializer.is_valid(raise_exception=True)
return attrs
This approach requires a little bit more code, but allows to combine validation of config_field with other related fields.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With