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 MyModelSerializer
validate
method for entire object being serialized by MyModelSerializer
Serializers 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_vars
stores 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