DRF中在Serializer的to_representation内使用ListSerializer的正确处理方式
处理DRF中单个请求创建多个对象的序列化问题
问题场景
需要接收嵌套JSON数组数据,批量创建多个对象,通过重写Serializer的create和to_representation方法实现,但运行时出现序列化错误。
原始代码
import json from rest_framework import serializers class Link: def __init__(self, title: str, description: str, url: str): self.title = title self.description = description self.url = url class LinkResponseSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField(required=False) url = serializers.CharField() class URLSerializer(serializers.Serializer): url = serializers.CharField() class LinkSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField() urls = URLSerializer(many=True) def create(self, validated_data): links = [] for obj in validated_data.pop("urls", []): links.append(Link(url=obj["url"], **validated_data)) return links def to_representation(self, instance): return LinkResponseSerializer(instance, many=True).data data = { "title": "Example", "description": "Exampe Description", "urls": [ {"url": "https://google.com"}, {"url": "https://yahoo.com"}, ] } serializer = LinkSerializer(data=data) serializer.is_valid(raise_exception=True) serializer.save() print(json.dumps(serializer.data, indent=4))
报错信息
Traceback (most recent call last): File "/tmp/.../test.py", line 54, in <module> print(json.dumps(serializer.data, indent=4)) ^^^^^^^^^^^^^^^ File "/tmp/.../venv/lib64/python3.12/site-packages/rest_framework/serializers.py", line 556, in data return ReturnDict(ret, serializer=self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/.../venv/lib64/python3.12/site-packages/rest_framework/utils/serializer_helpers.py", line 19, in __init__ super().__init__(*args, **kwargs) ValueError: too many values to unpack (expected 2)
错误原因
DRF的普通Serializer默认对应单个实例,其data属性会用ReturnDict(键值对结构)包装序列化结果。但你的create方法返回了实例列表,to_representation也返回列表结构,导致ReturnDict初始化时无法解析列表数据,从而报错。
你之前重写data属性返回ReturnList的临时方案,会破坏DRF可浏览API的逻辑(API期望单个对象的输入表单),因此不推荐。
正确处理方案
方案一:拆分输入处理与输出序列化(推荐)
将输入验证、实例创建的逻辑,与结果序列化的逻辑拆分,既符合DRF设计规范,又兼容可浏览API:
import json from rest_framework import serializers class Link: def __init__(self, title: str, description: str, url: str): self.title = title self.description = description self.url = url class LinkResponseSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField(required=False) url = serializers.CharField() class URLSerializer(serializers.Serializer): url = serializers.CharField() class LinkCreateSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField() urls = URLSerializer(many=True) def create(self, validated_data): links = [] urls = validated_data.pop("urls", []) for obj in urls: links.append(Link(url=obj["url"], **validated_data)) return links # 使用示例 data = { "title": "Example", "description": "Exampe Description", "urls": [ {"url": "https://google.com"}, {"url": "https://yahoo.com"}, ] } # 处理输入并创建实例 create_serializer = LinkCreateSerializer(data=data) create_serializer.is_valid(raise_exception=True) links = create_serializer.save() # 序列化输出结果 response_serializer = LinkResponseSerializer(links, many=True) print(json.dumps(response_serializer.data, indent=4))
方案二:在通用视图中适配
如果使用DRF通用视图(如CreateAPIView),可以在视图层完成输入输出的序列化适配,保证可浏览API正常工作:
from rest_framework.views import APIView from rest_framework.response import Response # 复用方案一中的LinkCreateSerializer和LinkResponseSerializer class LinkCreateView(APIView): def post(self, request): # 验证输入并创建实例 create_serializer = LinkCreateSerializer(data=request.data) create_serializer.is_valid(raise_exception=True) links = create_serializer.save() # 序列化输出列表结果 response_serializer = LinkResponseSerializer(links, many=True) return Response(response_serializer.data)
方案三:自定义ListSerializer(适配批量场景)
如果需要直接用单个Serializer处理,可通过自定义ListSerializer实现输入输出的统一:
import json from rest_framework import serializers class Link: def __init__(self, title: str, description: str, url: str): self.title = title self.description = description self.url = url class LinkResponseSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField(required=False) url = serializers.CharField() class URLSerializer(serializers.Serializer): url = serializers.CharField() class LinkListSerializer(serializers.ListSerializer): def create(self, validated_data): # 从单个输入数据中解析urls,生成多个实例 data = validated_data[0] links = [] urls = data.pop("urls", []) for obj in urls: links.append(Link(url=obj["url"], **data)) return links class LinkSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) description = serializers.CharField() urls = URLSerializer(many=True) class Meta: list_serializer_class = LinkListSerializer def to_representation(self, instance): return LinkResponseSerializer(instance, many=True).data # 使用示例:初始化时指定many=True data = { "title": "Example", "description": "Exampe Description", "urls": [ {"url": "https://google.com"}, {"url": "https://yahoo.com"}, ] } serializer = LinkSerializer(data=[data], many=True) serializer.is_valid(raise_exception=True) serializer.save() print(json.dumps(serializer.data, indent=4))
内容的提问来源于stack exchange,提问作者hardhypochondria
相关产品推荐
相关产品推荐

