DRF序列化报错:预期字典却得到列表的问题解决
DRF序列化器适配外部API数据报错的解决方案
问题背景
我正在开发一个基于Django+DRF的项目,通过Celery任务从外部API拉取剧集数据并保存到数据库。外部API返回格式如下:
{"shows": ["Boy Meets World", "Everybody Hates Chris", ...]}
Celery任务简化代码:
@shared_task def fetch_and_save_all_shows(): api_url = TV_SHOW_URL shows_url = f"{api_url}/quotes/shows" try: response = requests.get(shows_url) data = response.json() if isinstance(data, dict) and 'shows' in data: show_names = data['shows'] if not isinstance(show_names, list): raise ValueError("Expected 'shows' to be a list.") # 转换为序列化器预期的格式 show_data = [{'shows': show} for show in show_names] serializer = AllTvShowsSerializer(data=show_data, many=True) serializer.is_valid(raise_exception=True) serializer.save() else: raise ValueError("Invalid response data format") except Exception as ex: return {"error": f"An error occurred: {str(ex)}"}
遇到两个错误:
- 设置
many=True时,报错:Invalid data. Expected a dictionary, but got list. - 设置
many=False时,报错:Invalid data. Expected a dictionary, but got str.
错误原因与修复方案
核心问题:序列化器定义与传入数据格式不匹配
根据报错信息和代码逻辑,问题出在序列化器预期的数据结构与你构造的show_data格式不匹配,分两种常见场景处理:
场景1:序列化器对应单个剧集模型
如果AllTvShowsSerializer是单个TvShow模型的ModelSerializer,模型中存储剧集名称的字段是name(而非shows):
修复步骤:
- 调整数据转换逻辑,使用模型字段名作为字典键:
# 把'shows'改为模型对应的字段名,比如'name' show_data = [{'name': show} for show in show_names] - 保持
many=True实例化序列化器(因为传入的是字典列表):serializer = AllTvShowsSerializer(data=show_data, many=True)
示例序列化器定义:
from rest_framework import serializers from .models import TvShow class AllTvShowsSerializer(serializers.ModelSerializer): class Meta: model = TvShow fields = ['name'] # 与模型字段一致
场景2:序列化器接收完整外部API响应结构
如果AllTvShowsSerializer是用来接收外部API返回的完整{"shows": [列表]}结构:
修复步骤:
- 无需拆分
show_names,直接传入外部API返回的原始data:# 移除show_data的构造逻辑,直接用data serializer = AllTvShowsSerializer(data=data) - 不要设置
many=True(因为传入的是单个字典)。
示例序列化器定义:
from rest_framework import serializers from .models import TvShow class AllTvShowsSerializer(serializers.Serializer): # 定义字段匹配外部API的结构:shows是字符串列表 shows = serializers.ListField(child=serializers.CharField(max_length=255)) def create(self, validated_data): # 批量创建/获取剧集,避免重复 show_names = validated_data['shows'] shows = [] for name in show_names: show, created = TvShow.objects.get_or_create(name=name) shows.append(show) return shows
额外检查点
- 确认外部API返回的
shows列表中所有元素都是字符串,无嵌套列表或其他类型。 - 确保序列化器的字段名与传入字典的键完全一致(大小写敏感)。
- 使用
ModelSerializer时,验证模型字段类型与传入数据类型匹配(比如字符串对应CharField)。
内容的提问来源于stack exchange,提问作者jessiepinkman
相关产品推荐
相关产品推荐

