如何用cattrs/attrs处理属性名与API返回键不匹配的对象构建
解决方案:通过cattrs自定义转换器实现字段映射与缺失处理
针对API返回字段与类属性名不匹配、字段可能缺失的场景,完全可以通过cattrs的自定义转换钩子实现批量处理,无需手动编写每个类的转换逻辑。以下是具体实现步骤:
1. 调整类定义,兼容字段缺失
首先给类的可选字段设置默认值或标记为Optional,避免因API返回字段缺失导致结构转换失败:
from attrs import define, field from typing import Optional import cattrs @define class PartialUser: id: Optional[int] = field(default=None) login: Optional[str] = field(default=None) @define class Info: language: Optional[str] = field(default=None) title: Optional[str] = field(default=None) delay: Optional[int] = field(default=None) broadcaster: Optional[PartialUser] = field(default=None) subscriber: Optional[PartialUser] = field(default=None) moderator: Optional[PartialUser] = field(default=None)
2. 编写通用转换逻辑,复用字段映射规则
实现一个通用函数,从API返回的带前缀字段(如broadcaster_id)中提取数据,构造PartialUser对象:
def structure_partial_user_from_prefix(data: dict, prefix: str) -> PartialUser: # 提取对应前缀的字段,处理字符串转int及空值 user_data = { "id": int(data.get(f"{prefix}_id")) if data.get(f"{prefix}_id") is not None else None, "login": data.get(f"{prefix}_login") } return cattrs.structure(user_data, PartialUser)
3. 注册类专属转换钩子,批量处理字段映射
针对Info类注册自定义结构钩子,复用上述通用逻辑,同时处理基础字段的类型转换(如delay字符串转int):
def structure_info(data: dict, _) -> Info: info_data = { # 处理基础字段 "language": data.get("language"), "title": data.get("title"), "delay": int(data.get("delay")) if data.get("delay") is not None else None, # 复用PartialUser通用转换逻辑 "broadcaster": structure_partial_user_from_prefix(data, "broadcaster"), "subscriber": structure_partial_user_from_prefix(data, "subscriber"), "moderator": structure_partial_user_from_prefix(data, "moderator") } return Info(**info_data) # 将钩子注册到cattrs默认转换器 cattrs.register_structure_hook(Info, structure_info)
4. 抽象逻辑适配70+类的批量处理
如果有大量类似结构的类,可以进一步抽象钩子生成函数,避免重复编写转换逻辑:
def create_prefix_based_hook(target_cls, field_prefix_map): """ field_prefix_map: 键为类属性名,值为API字段前缀,例如{"broadcaster": "broadcaster"} """ def hook(data: dict, _): instance_data = {} # 处理PartialUser类型的属性 for attr_name, prefix in field_prefix_map.items(): instance_data[attr_name] = structure_partial_user_from_prefix(data, prefix) # 处理基础属性,自动适配类型转换 for field in target_cls.__attrs_attrs__: if field.name not in field_prefix_map: value = data.get(field.name) if field.type is int and value is not None: value = int(value) instance_data[field.name] = value return target_cls(**instance_data) return hook # 给Info类注册钩子,其他类可直接复用该方式 cattrs.register_structure_hook(Info, create_prefix_based_hook(Info, { "broadcaster": "broadcaster", "subscriber": "subscriber", "moderator": "moderator" }))
5. 测试转换效果
执行结构转换代码,验证结果:
data = { "data": [ { "broadcaster_id": "123", "broadcaster_login": "Sam", "language": "en", "subscriber_id": "1234", "subscriber_login": "Dave", "moderator_id": "12345", "moderator_login": "Tom", "delay": "0", "title": "Weekend Events" } ] } instance = cattrs.structure(data["data"][0], Info) print(instance) # 输出:Info(language='en', title='Weekend Events', delay=0, broadcaster=PartialUser(id=123, login='Sam'), subscriber=PartialUser(id=1234, login='Dave'), moderator=PartialUser(id=12345, login='Tom'))
内容的提问来源于stack exchange,提问作者SimonT
相关产品推荐
相关产品推荐

