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

DRF中FormData传文件时嵌套对象数组解析失败问题

DRF处理FormData嵌套对象数组+文件上传的解决方案

DRF默认的MultiPartParser/FormParser无法自动解析FormData中的嵌套对象数组格式(如product_timings[0][start_time]),会将这类参数解析为扁平键值对,导致嵌套序列化器无法识别结构,触发必填字段验证错误。以下是两种无需Base64或单独JSON字段的解决方案:

方案一:重写序列化器to_internal_value方法(轻量适配)

在序列化器内部手动处理FormData的扁平参数,转换为嵌套数组结构:

class ProductCreateApi(APIView):
    permission_classes = [permissions.DjangoModelPermissions]
    queryset = Product.objects.all().order_by("-created_at")
    parser_class = [MultiPartParser, FormParser, JSONParser, FileUploadParser]
    
    class ProductCreateSerializer(serializers.ModelSerializer):
        class ProductCreateProductTimingSerializer(serializers.ModelSerializer):
            class Meta:
                model = ProductTiming
                fields = ['start_time', 'end_time']
        
        product_timings = ProductCreateProductTimingSerializer(write_only=True, many=True)
        product_images = serializers.ListField(
            child=serializers.ImageField(allow_empty_file=False, use_url=False),
            write_only=True
        )

        class Meta:
            model = Product
            fields = '__all__'
        
        def to_internal_value(self, data):
            # 提取并重组product_timings参数
            timings_data = {}
            for key, value in data.items():
                if key.startswith('product_timings['):
                    # 拆分索引与字段名
                    index = int(key.split('[')[1].rstrip(']'))
                    field = key.split('[')[2].rstrip(']')
                    # 按索引分组数据
                    if index not in timings_data:
                        timings_data[index] = {}
                    timings_data[index][field] = value
            
            # 转换为有序列表
            data['product_timings'] = [timings_data[idx] for idx in sorted(timings_data.keys())] if timings_data else []
            
            return super().to_internal_value(data)

使用说明

保持Postman中的参数格式不变:

  • product_timings[0][start_time]: 09:30:00
  • product_timings[0][end_time]: 09:30:00
    多组时序可依次使用product_timings[1][start_time]等连续索引。

方案二:自定义嵌套FormData解析器(通用方案)

如果多个视图需要处理此类嵌套格式,可自定义解析器统一处理:

import re
from django.http import QueryDict
from rest_framework.parsers import MultiPartParser

class NestedMultiPartParser(MultiPartParser):
    def parse(self, stream, media_type=None, parser_context=None):
        data = super().parse(stream, media_type, parser_context)
        parsed_data = {}
        
        for key, value in data.data.items():
            current = parsed_data
            key_parts = re.findall(r'(\w+|\d+)', key)
            if not key_parts:
                continue
            
            # 逐层构建嵌套结构
            for part in key_parts[:-1]:
                if part.isdigit():
                    part = int(part)
                    # 初始化列表结构
                    if not isinstance(current, list):
                        current = []
                        # 绑定到父节点
                        if len(key_parts) == 2:
                            parsed_data[key_parts[0]] = current
                        else:
                            parent_current = parsed_data[key_parts[0]]
                            for p in key_parts[1:-1]:
                                parent_current = parent_current[int(p)] if p.isdigit() else parent_current[p]
                            parent_current.append(current)
                    # 确保列表长度覆盖当前索引
                    while len(current) <= part:
                        current.append({})
                    current = current[part]
                else:
                    # 构建字典层级
                    if part not in current:
                        current[part] = {}
                    current = current[part]
            
            # 设置最终字段值
            last_part = key_parts[-1]
            if isinstance(current, list):
                last_part = int(last_part)
                while len(current) <= last_part:
                    current.append({})
                current[last_part] = value
            else:
                current[last_part] = value
        
        # 更新解析后的数据
        data.data = QueryDict('', mutable=True)
        data.data.update(parsed_data)
        return data

视图中使用解析器

class ProductCreateApi(APIView):
    permission_classes = [permissions.DjangoModelPermissions]
    queryset = Product.objects.all().order_by("-created_at")
    # 将自定义解析器放在首位
    parser_class = [NestedMultiPartParser, MultiPartParser, FormParser, JSONParser, FileUploadParser]
    
    # 序列化器代码保持原有结构不变
    class ProductCreateSerializer(serializers.ModelSerializer):
        # ... 原有代码 ...

补充注意事项

  • product_images字段上传多文件时,Postman中每个文件的键需设置为product_images[],DRF的ListField会自动识别为文件列表。
  • 嵌套数组的索引需保持连续(0、1、2...),避免解析出空数据项。

内容的提问来源于stack exchange,提问作者Krishnadas PC

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:54:57