如何将DRF的JSONField用作Serializer并保留验证逻辑?
解决方案
方案1:通过Context传递Instance到自定义Field
DRF的Field可以通过self.context访问所属Serializer的上下文,其中默认包含instance对象。你可以直接在MetadataField中取出instance用于动态验证:
from rest_framework.serializers import JSONField, Serializer, ValidationError class MetadataField(JSONField): def to_internal_value(self, raw_data): data = super().to_internal_value(raw_data) # 从context中获取当前序列化的实例 instance = self.context.get('instance') # 基于instance执行动态验证逻辑 if instance and instance.status != 'active': raise ValidationError("仅活跃状态的实例允许修改元数据") # 其他复杂验证逻辑(可调用类内其他方法) self._validate_metadata_structure(data) return data def _validate_metadata_structure(self, data): if not isinstance(data, dict): raise ValidationError("元数据必须为字典格式") # 更多结构验证逻辑... class ItemSerializer(Serializer): metadata = MetadataField()
方案2:用嵌套Serializer替代自定义Field
既然你需要Serializer的额外特性(访问instance、自动生成Schema),直接把元数据的逻辑写成独立的嵌套Serializer更适配需求。Serializer本身就是Field的子类,完全可以作为字段使用,既保留复用性,又能天然利用Serializer的所有功能:
from rest_framework.serializers import Serializer, JSONField, ValidationError from django.utils import timezone from datetime import timedelta class MetadataSerializer(Serializer): # 若元数据是完全动态结构,直接用JSONField承载 data = JSONField() def validate_data(self, value): # 直接访问self.instance进行动态验证 if self.instance and self.instance.created_at < timezone.now() - timedelta(days=7): raise ValidationError("过期实例无法修改元数据") # 其他复杂验证逻辑 self._check_metadata_keys(value) return value def _check_metadata_keys(self, data): forbidden_keys = ['id', 'created_at'] if any(key in data for key in forbidden_keys): raise ValidationError(f"元数据禁止包含以下字段: {','.join(forbidden_keys)}") class ItemSerializer(Serializer): # 直接使用嵌套Serializer作为字段,自动支持instance访问和Schema生成 metadata = MetadataSerializer()
这个方案的核心优势:
- 无需额外传递,直接通过
self.instance访问当前实例 - DRF的Schema生成器会自动识别嵌套Serializer,生成符合要求的动态Schema
- 验证逻辑完全独立封装,可在多个Serializer中直接复用
补充:自定义Field的Schema生成支持
如果坚持使用自定义Field,需要实现get_schema_field方法来适配DRF的Schema生成机制:
from rest_framework.schemas.openapi import AutoSchema class MetadataField(JSONField): def get_schema_field(self, schema): return { 'type': 'object', 'properties': { 'tag': {'type': 'string'}, 'expire_at': {'type': 'string', 'format': 'date-time'} }, 'example': {'tag': 'promotion', 'expire_at': '2024-12-31T23:59:59Z'} }
内容的提问来源于stack exchange,提问作者Jessie
相关产品推荐
相关产品推荐

