Python3中带泛型集合的类序列化/反序列化问题:JSON库为何将ColumnDef反序列化为字典而非类实例
为什么JSON反序列化后嵌套的NamedTuple变成了字典?
这个问题的核心原因很直白:Python标准库的json模块完全不知道你定义的ColumnDef和RunConfig这些NamedTuple类型。它只会遵循默认规则解析JSON:
- JSON对象(
{})会被转成Python字典 - JSON数组(
[])会被转成Python列表
所以当你用json.loads解析完JSON字符串后,loaded_json['columns']只是一个装着字典的列表,而非ColumnDef实例的列表。之后你直接把这个字典列表传给RunConfig(**loaded_json),自然就会得到一个包含字典的columns字段,而非你期望的ColumnDef实例集合。
下面给出几种实用的解决方法:
方法1:手动转换嵌套字段(最简单直接)
在read_run_config函数里,手动遍历columns列表,把每个字典转换成ColumnDef实例:
def read_run_config(json_config: str) -> RunConfig: loaded_json = json.loads(json_config) # 手动将每个字典转为ColumnDef实例 loaded_json['columns'] = [ColumnDef(**col) for col in loaded_json['columns']] return RunConfig(**loaded_json)
修改后运行测试,输出会变成RunConfig(columns=[ColumnDef(name='id')]),断言也能成功匹配。
方法2:使用json模块的object_hook(更通用)
如果你的数据结构更复杂,可以自定义一个钩子函数,让json.loads自动识别并转换对应的类型。需要通过字段特征区分不同的NamedTuple:
def namedtuple_hook(dct): # 判断是否是ColumnDef的结构(仅包含name字段) if 'name' in dct and len(dct) == 1: return ColumnDef(**dct) # 判断是否是RunConfig的结构(仅包含columns字段) if 'columns' in dct and len(dct) == 1: return RunConfig(**dct) # 其他结构返回原字典 return dct def read_run_config(json_config: str) -> RunConfig: return json.loads(json_config, object_hook=namedtuple_hook)
这种方法的优势是可以扩展到更多嵌套类型,让解析过程更自动化。
方法3:使用第三方库(推荐复杂场景)
如果你的项目需要处理更多序列化/反序列化需求(比如字段验证、复杂嵌套类型),推荐使用专门的库,比如Pydantic。它原生支持JSON解析,还能自动做类型校验:
from pydantic import BaseModel class ColumnDef(BaseModel): name: str class RunConfig(BaseModel): columns: list[ColumnDef] # 也可以用Sequence[ColumnDef] def read_run_config(json_config: str) -> RunConfig: return RunConfig.model_validate_json(json_config)
修改后测试代码无需改动即可通过,而且如果JSON格式不符合要求(比如缺少name字段、columns不是数组),Pydantic会抛出明确的错误,非常适合生产环境。
内容的提问来源于stack exchange,提问作者Capacytron
相关产品推荐
相关产品推荐

