You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django多对多序列化:无需自定义序列化器反转嵌套JSON?

问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:49:22