JSONField 嵌套表示的序列化程序

Serializer for JSONField nested representation

在我的应用程序中,有一个模型在其字段之一中存储配置。该字段定义为 JSONField。我有一个严格的结构来定义该字段的内容应该是什么样子,但我正在努力寻找一种方法来序列化它以验证 API 请求中的数据。

目前可行的解决方案,但不验证 config_field 中的内容,是盲目接受任何符合 json 对象的内容:

我的模型的简化版本:

class MyModel(models.Model):
    config_field = JSONField(...)
    ...

为了这个问题,这里是存储在 config_field 中的数据结构的简化版本:

{"some_config_int": 42, "some_config_vars": [{"id": 1}, {"id": 2}]}

这是我的序列化程序的简化版本:

class MyModelSerializer(serializers.ModelSerializer):
        config_field = serializers.JSONField(required=False)
        class Meta:
            model = MyModel
            fields = ('config_field', ...)

虽然我想要实现的是为 config_field 中的嵌套表示 (reference to DRF documentation) 提供一个序列化程序。到目前为止我尝试过的(但不起作用):

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', ...)

这样,POST/PUT 具有配置的对象将是可选的,但如果 config_field 在请求正文中,则应提供整个嵌套对象。

您正在针对 config_field 字段 发送数据 ,因此,您的数据应包含该键。所以有效载荷应该如下

<b>{"config_field": </b>{"some_config_int": 42, "some_config_vars": ["foo", "bar"]}}

更新 1

在序列化程序中使用 DictField() 作为,

<b>VALID_DICT_KEYS = ['foo_1']</b>


class ConfigFieldsSerializer(serializers.Serializer):
    some_config_int = serializers.IntegerField(required=True)
    some_config_vars = serializers.ListField(<b>child=serializers.DictField()</b>, required=True)

    <b>def validate(self, attrs):
        attrs = super().validate(attrs)
        some_config_vars = attrs['some_config_vars']
        keys_list = []
        for item in some_config_vars:
            keys_list.extend(list(item.keys()))
        unwanted_keys = set(keys_list) - set(VALID_DICT_KEYS)
        if unwanted_keys:
            raise serializers.ValidationError("raise error with some msg")
        return attrs</b>


class MyModelSerializer(serializers.Serializer):
    config_field = ConfigFieldsSerializer(required=False)

    class Meta:
        fields = ('config_field',)


data = {<b>'config_field'</b>: {"some_config_int": 42, "some_config_vars": [{"foo_1": "bar"}, {"foo_2": "honey"}]}}
serializer = MyModelSerializer(data=data)
serializer.is_valid(True)
print(serializer.data)

在尝试了几种可能的解决方案之后,我想指出 2 个最简单且最重要的解决方案 不需要 覆盖 create 既不用于 MyModelSerializer 也不用于内部序列化程序的方法:

  1. 覆盖 MyModelSerializer
  2. config_field 的字段验证方法
  3. 重写 validate 方法,用于由 MyModelSerializer
  4. 序列化的整个对象

表示 config_field 内部内容的序列化程序对于两种解决方案都是相同的:

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 why many=True.


解决方案 1

覆盖 MyModelSerializerconfig_field 的字段验证方法。在给定示例的情况下,序列化程序的最终代码将是:

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

此方法首先使用默认 JSONFieldSerializer 验证 config_field 并在内容不是有效的 JSON 对象时引发异常。

如果 JSONFieldSerializer 没有引发异常,则调用 validate_custom_fields 并将字段内容传递给 ConfigFieldsSerializer 并为自身和所有嵌套的序列化程序验证所有内容。


解决方案 2

重写 validate 方法以用于由 MyModelSerializer 序列化的整个对象。在给定示例的情况下,序列化程序的最终代码将是:

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

这种方法需要更多的代码,但允许将 config_field 的验证与其他相关字段结合起来。