在实际开发中,我们经常需要在不同的数据结构(如字典、SQLAlchemy 模型、Pydantic 模型、Dataclass 等)之间进行转换。为了减少重复代码并提高转换的灵活性,设计一套通用的转换工具让代码更简洁。本文将通过两个核心函数实现数据对象与模型的双向转换。
一、为什么需要数据结构间的互转?
-
统一数据操作:不同的场景中使用的结构可能不同,但处理逻辑需要一致。
-
API数据通常以字典形式传递。 -
数据库查询返回的是
SQLAlchemy模型。 -
Pydantic和Dataclass是开发中常用的强类型模型。 -
减少手动转换:手动编写转换逻辑容易出错且效率低。
二、核心工具概览
本文将讲解两个核心函数:
-
data_to_model:将各种数据对象(字典、SQLAlchemy、Pydantic、Dataclass 等)转换为目标模型。 -
model_to_data:将各种模型对象(SQLAlchemy、Pydantic、Dataclass 等)还原为原始数据(字典或列表)。
三、从数据对象到模型:data_to_model
函数功能
data_to_model 可以将输入数据(字典、模型对象等)转换为目标模型(Pydantic、SQLAlchemy 或 Dataclass)。支持单个对象和列表对象的递归转换。
核心代码
@classmethod
def data_to_model(
cls,
data_obj: Union[
dict,
BaseOrmTable,
BaseModel,
dataclass,
List[dict],
List[BaseOrmTable],
List[BaseModel],
],
to_model: Type[Union[BaseModel, BaseOrmTable, dataclass]],
) -> Union[BaseModel, List[BaseModel], List[BaseOrmTable], List[dataclass], None]:
"""
将数据对象转换成 pydantic 或 sqlalchemy 模型对象, 如果是数据库库表模型对象则调用to_dict()后递归
Args:
data_obj: 支持 字典对象, pydantic、sqlalchemy模型对象, 列表对象
to_model: 转换后数据模型
Notes:
- 对于实现了 to_dict() 方法的模型对象,将调用该方法返回字典。
returns:
转换后的对象
"""
if isinstance(data_obj, dict):
# 字典处理
return to_model(**data_obj)
elif isinstance(data_obj, BaseOrmTable):
# 数据库表模型对象处理, to_dict()后递归调用
return cls.data_to_model(data_obj.to_dict(), to_model=to_model)
elif isinstance(data_obj, BaseModel):
# pydantic v2 模型对象处理, model_dump 后递归调用
return cls.data_to_model(data_obj.model_dump(), to_model=to_model)
elif dataclasses.is_dataclass(data_obj):
# dataclass 模型对象处理, asdict() 后递归调用
return cls.data_to_model(asdict(data_obj), to_model=to_model)
elif hasattr(data_obj, "to_dict"):
# 如果模型对象有 to_dict 方法,调用该方法返回字典
return cls.data_to_model(data_obj.to_dict(), to_model=to_model)
elif isinstance(data_obj, list):
# 列表处理
return [cls.data_to_model(item, to_model=to_model) for item in data_obj]
else:
raise ValueError(f"不支持此{data_obj}类型的序列化转换")
转换流程
-
字典处理:直接解包为目标模型。
-
SQLAlchemy 模型处理:调用
to_dict方法后递归处理。 -
Pydantic 模型处理:使用
model_dump方法生成字典后递归处理。 -
Dataclass 处理:通过
asdict方法生成字典后递归处理。 -
列表处理:逐个递归处理列表中的元素。
-
其他实现了
to_dict方法的调用其to_dict递归处理
使用示例
先定义一些 pydantic、dataclass 等模型对象
from dataclasses import dataclass
from pydantic import BaseModel
from sqlalchemy import Column, String
from py_tools.connections.db.mysql import BaseOrmTable
from py_tools.utils import SerializerUtil
# sqlalchemy 示例
class UserTable(BaseOrmTable):
__tablename__ = "user"
username = Column(String(20))
email = Column(String(50))
# Pydantic 示例
class UserModel(BaseModel):
id: int
username: str
email: str
@dataclass
class UserDataclass:
id: int
username: str
email: str
class UserCustomModel:
def __init__(self, id: int, username: str, email: str):
self.id = id
self.username = username
self.email = email
def to_dict(self):
return {"id": self.id, "username": self.username, "email": self.email}
数据转换demo
def data_to_model_demo():
user_table_obj = UserTable(id=2, username="wang", email="wang@example.com")
user_model_obj = UserModel(id=3, username="zack", email="zack@example.com")
user_dataclass_obj = UserDataclass(id=4, username="lisa", email="lisa@example.com")
user_custom_model = UserCustomModel(id=5, username="lily", email="lily@example.com")
user_infos = [
{"id": 1, "username": "hui", "email": "hui@example.com"},
user_table_obj,
user_model_obj,
user_dataclass_obj,
user_custom_model,
]
print("data_to_model")
user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserModel)
print(type(user_models), user_models)
user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserTable)
print(type(user_models), user_models)
user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserDataclass)
print(type(user_models), user_models)
user_models = SerializerUtil.data_to_model(data_obj=user_infos, to_model=UserCustomModel)
print(type(user_models), user_models)
user_model = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserModel)
user_table = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserTable)
user_dataclass = SerializerUtil.data_to_model(data_obj=user_infos[0], to_model=UserDataclass)
print(type(user_model), user_model)
print(type(user_table), user_table)
print(type(user_dataclass), user_dataclass)
转换效果如下
四、从模型到数据对象:model_to_data
函数功能
model_to_data 将模型对象(SQLAlchemy、Pydantic 或 Dataclass)转换为字典或列表,适用于需要原始数据结构的场景。
核心代码
@classmethod
def model_to_data(
cls,
model_obj: Union[
BaseModel,
BaseOrmTable,
dataclass,
List[BaseModel],
List[BaseOrmTable],
List[dataclass],
],
) -> Union[dict, List[dict], None]:
"""
将 Pydantic 模型或 SQLAlchemy 模型对象转换回原始字典或列表对象。
Args:
model_obj: 支持 Pydantic 模型对象、SQLAlchemy 模型、dataclass 对象,或者它们的列表
Notes:
- 对于实现了 to_dict() 方法的模型对象,将调用该方法返回字典。
Returns:
转换后的字典或列表
"""
if isinstance(model_obj, dict):
return model_obj
if isinstance(model_obj, RowMapping):
return dict(model_obj)
elif isinstance(model_obj, BaseModel):
# Pydantic 模型对象处理,model_dump() 返回字典
return model_obj.model_dump()
elif isinstance(model_obj, BaseOrmTable):
# SQLAlchemy 模型对象处理,to_dict() 返回字典
return model_obj.to_dict()
elif dataclasses.is_dataclass(model_obj):
# dataclass 模型对象处理, asdict() 返回字典
return asdict(model_obj)
elif hasattr(model_obj, "to_dict"):
# 如果模型对象有 to_dict 方法,调用该方法返回字典
return model_obj.to_dict()
elif isinstance(model_obj, list):
# 列表处理,递归转换每个元素
return [cls.model_to_data(item) for item in model_obj]
else:
raise ValueError(f"不支持此{model_obj}类型的反序列化转换")
转换流程
-
Pydantic 模型:使用
model_dump方法生成字典。 -
SQLAlchemy 模型:通过
to_dict方法生成字典。 -
Dataclass:通过
asdict方法生成字典。 -
列表处理:递归处理每个元素。
-
其他实现了
to_dict方法的调用其to_dict生成字典
使用示例
def model_to_data_demo():
user_table_obj = UserTable(id=2, username="wang", email="wang@example.com")
user_model_obj = UserModel(id=3, username="zack", email="zack@example.com")
user_dataclass_obj = UserDataclass(id=4, username="lisa", email="lisa@example.com")
user_custom_model = UserCustomModel(id=5, username="lily", email="lily@example.com")
user_infos = [
{"id": 1, "username": "hui", "email": "hui@example.com"},
user_table_obj,
user_model_obj,
user_dataclass_obj,
user_custom_model,
]
# model_to_data
print("model_to_data")
user_infos = SerializerUtil.model_to_data(user_infos)
print(type(user_infos), user_infos)
user_info = SerializerUtil.model_to_data(user_model_obj)
print(type(user_info), user_info)
user_info = SerializerUtil.model_to_data(user_table_obj)
print(type(user_info), user_info)
user_info = SerializerUtil.model_to_data(user_dataclass_obj)
print(type(user_info), user_info)
user_info = SerializerUtil.model_to_data(user_custom_model)
print(type(user_info), user_info)
转换效果如下:
五、总结
-
data_to_model和model_to_data提供了统一的数据转换能力,能够在字典、SQLAlchemy 模型、Pydantic 模型和 Dataclass 之间灵活转换。 -
在 Web 开发中,这两个函数可以用于 API 数据解析、数据库模型转换、响应数据格式化等多个场景。
通过这些工具,开发者可以显著提高代码的复用性和可维护性,同时让代码更清晰、简洁
六、Github源代码
HuiDBK/py-tools: 打造 Python 开发常用的工具,让Coding变得更简单 (github.com)
模型对象互转工具的封装已集成到
hui-tools中,安装 hui-tools 后可以直接使用。