Django多对多序列化:无需自定义序列化器反转嵌套JSON?
我在Django项目中使用selected_related获取关联表数据,但关联数据嵌套在每条子记录中。我希望反转这种关联关系的JSON展示结构,请问无需自定义序列化器是否可以实现?
当前输出结构
[ { "id": 1, "item": " Sub item", "parent_items": { "id": 1, "description": "Parent Item Number 1" } }, { "id": 2, "item": " Sub item 2", "parent_items": { "id": 2, "description": "Parent Item Number 2" } } ]
期望反转后的结构
[ { "parent_items": "Parent Item Number 1", "id": 1, "item": [ {"id": 1, "description": "Sub Item 1"}, {"id": 2, "description": "Sub Item 2 "} ] }, { "parent_items": " Parent Item Number 2", "id": 2, "item": [ {"id": 1, "description": "SubItem Item 2"} ] } ]
相关代码
模型
class MainItem(models.Model): id = models.IntegerField(primary_key=True) description = models.CharField(max_length=50) class SubItem(models.Model): """Equates to parent items""" id = models.IntegerField(primary_key=True) main_item = models.ForeignKey(MainItem, null=False) description = models.CharField(max_length=50) class Main(models.Model): id = models.AutoField(primary_key=True) href = models.CharField(max_length=250) sub_items = models.ManyToManyField(SubItem)
序列化器
class SubItemSerializer(ModelSerializer): class Meta: model = SubItem fields = '__all__' depth = 1
视图
class ItemsView(APIView): def get(self, request): items = SubItems.objects.select_related('main_item').all() issue_ser = SubItemSerializer(issues, many=True) return Response(issue_ser.data)
要直接用默认的序列化器实现你想要的结构反转,基本是做不到的。原因很简单:DRF的ModelSerializer是和你查询的模型绑定的,你现在从SubItem出发查询,默认就会把MainItem嵌套在每条子记录里;而你要的是把父项作为顶层结构,子项嵌套在父项里,这本质上需要从MainItem的角度来组织数据,默认序列化器不会自动帮你做这个反转。
顺便提一下,你当前的视图代码里有几个小错误:SubItems.objects应该是SubItem.objects(模型类名是单数),issue_ser = SubItemSerializer(issues, many=True)里的issues应该是items(变量名不一致),这些需要修正才能正常运行。
可行方案(极简序列化器配置)
虽然完全依赖自动生成的序列化器不行,但写一个极简的ModelSerializer(只是配置字段,不算复杂自定义)是最合理的方式,步骤如下:
1. 给外键添加反向关联名称
修改SubItem模型的外键字段,添加related_name,这样能在MainItem上直接访问对应的子项:
class SubItem(models.Model): id = models.IntegerField(primary_key=True) # 添加related_name,方便反向查询子项 main_item = models.ForeignKey(MainItem, null=False, related_name='sub_items') description = models.CharField(max_length=50)
2. 创建简单的MainItem序列化器
这个序列化器仅指定字段和关联深度,非常简洁:
from rest_framework.serializers import ModelSerializer class MainItemSerializer(ModelSerializer): # 重命名description为parent_items,匹配你期望的字段名 parent_items = serializers.CharField(source='description') class Meta: model = MainItem fields = ['id', 'parent_items', 'sub_items'] # depth=1 自动嵌套关联的SubItem数据 depth = 1
3. 修改视图查询MainItem而非SubItem
现在查询父项,并用prefetch_related优化子项查询(反向多对一关系用这个更合适):
class ItemsView(APIView): def get(self, request): # 查询所有MainItem,并预加载对应的子项 main_items = MainItem.objects.prefetch_related('sub_items').all() ser = MainItemSerializer(main_items, many=True) return Response(ser.data)
这样得到的输出结构就和你期望的完全一致了。
完全不用自定义序列化器的替代方案(不推荐)
如果硬要完全不写自己的Serializer类,只能手动构造数据,但这种方式代码冗余、维护性差,还会失去DRF序列化器的验证、格式处理等优势:
class ItemsView(APIView): def get(self, request): result = [] for main_item in MainItem.objects.all(): # 手动查询并构造子项数据 sub_items = list(main_item.sub_items.values('id', 'description')) result.append({ "id": main_item.id, "parent_items": main_item.description, "item": sub_items }) return Response(result)
总结:写一个极简的ModelSerializer是最优解,几乎不算“自定义”开发;完全不用序列化器的方式性价比极低,不推荐。
内容的提问来源于stack exchange,提问作者user2420225

