Pydantic使用对象作为字典键时JSON序列化失败问题咨询
Pydantic非字符串字典键序列化解决方案
核心问题原因
标准JSON规范要求对象键必须为字符串,Python标准库json.dumps的default参数仅处理无法序列化的值,不会处理字典键,这是底层限制。Pydantic的json_encoders配置仅作用在值序列化阶段,因此不会对字典键生效。
可行解决方案
不需要额外找其他数据结构,JSON层面仅支持字符串键的对象,以下方案都不需要自定义全局JSON编码器,符合业务限制:
方案1:字段级自定义序列化器(推荐)
直接给字典字段加序列化逻辑,在Pydantic序列化阶段自动转换键为字符串,无需修改数据构造逻辑。
Pydantic v2 示例:
from pydantic import BaseModel, field_serializer from typing import Dict # 你的自定义Identifier类型 class Identifier: def __init__(self, val): self.val = val def __str__(self): # 你自定义的转字符串逻辑 return f"warehouse_{self.val}" class WarehouseItem(BaseModel): name: str address: str class Report(BaseModel): warehouses: Dict[Identifier, WarehouseItem] @field_serializer("warehouses") def serialize_warehouse_keys(self, value: Dict[Identifier, WarehouseItem]) -> Dict[str, WarehouseItem]: # 自动将所有Identifier类型的键转为字符串 return {str(k): v for k, v in value.items()} # 测试用例 warehouses = [ {"id": Identifier(1), "name": "一号仓", "address": "北京市朝阳区"}, {"id": Identifier(2), "name": "二号仓", "address": "上海市浦东新区"} ] report = Report( warehouses={w["id"]: WarehouseItem(name=w["name"], address=w["address"]) for w in warehouses} ) print(report.model_dump_json(indent=2))
Pydantic v1 示例:
from pydantic import BaseModel from typing import Dict class Identifier: def __init__(self, val): self.val = val def __str__(self): return f"warehouse_{self.val}" class WarehouseItem(BaseModel): name: str address: str class Report(BaseModel): warehouses: Dict[Identifier, WarehouseItem] def dict(self, **kwargs): raw = super().dict(**kwargs) # 序列化前预处理字典键 raw["warehouses"] = {str(k): v for k, v in self.warehouses.items()} return raw # 调用json()会自动先走dict()预处理,不会触发键类型错误 report = Report(...) print(report.json(indent=2))
方案2:构造数据时预处理键(最简)
不需要修改Pydantic模型,构造字典时直接将非字符串键转成目标格式,将模型的字典字段类型定义为Dict[str, X]即可:
# 构造数据时直接转键 warehouse_data = { str(warehouse.id): { "name": warehouse.name, "address": warehouse.address, } for warehouse in warehouses } # 模型字段直接定义为字符串键,无任何序列化问题 class Report(BaseModel): warehouses: Dict[str, WarehouseItem]
方案3:自定义可序列化字典子类
如果有多处同类场景,可以封装一个自动转键的字典子类,Pydantic会自动识别序列化:
from collections import UserDict class AutoStrKeyDict(UserDict): def __init__(self, data=None): super().__init__({str(k): v for k, v in (data or {}).items()}) # 模型中直接使用该类型 class Report(BaseModel): warehouses: AutoStrKeyDict[WarehouseItem]
内容的提问来源于stack exchange,提问作者Lacrymology
相关产品推荐
相关产品推荐

