如何在Flask-Restplus中合并两张表数据返回单一API响应?
看起来你遇到的问题是因为Flask-Restplus的序列化逻辑把查询返回的(User, Device)元组里的两个对象分别处理了,所以才生成了重复的条目。咱们一步步来解决这个问题:
1. 先搞清楚问题根源
你当前的查询返回的是(User, Device)元组的列表,而@api.marshal_list_with(_details, envelope='data')会遍历这个列表里的每一个元素——也就是每个元组里的User和Device对象,分别用_details模型序列化,自然就出现了两条重复id/name但token不同的记录。
2. 方案一:手动合并查询结果(直观易理解)
我们可以先把查询到的User和Device字段合并成单个字典,再交给序列化器处理:
调整业务逻辑代码
在你的user(data=data)函数里,把查询结果转换成合并后的字典列表:
def user(data): details = db.session.query(User, Device).filter(User.id == Device.id)\ .filter(User.email == data['email']).all() # 合并每个(User, Device)元组为一个符合DTO结构的字典 combined_results = [] for user_obj, device_obj in details: combined = { "id": user_obj.id, "name": user_obj.name, "token": device_obj.token if device_obj else None } combined_results.append(combined) # 如果需要你期望的双层数组结构,就返回[combined_results],否则直接返回combined_results return combined_results
保持现有DTO不变
你的_details模型已经完全匹配合并后的字典结构,所以控制器上的@api.marshal_list_with(_details, envelope='data')可以直接保留。
3. 方案二:利用Flask-Restplus的attribute参数(更简洁)
如果不想手动合并字典,可以修改DTO模型,指定每个字段对应的数据源路径:
定义新的合并输出模型
在UserDto里新增一个适配元组结构的模型:
class UserDto: # ... 原有input模型保持不变 # 适配(User, Device)元组的输出模型 combined_details = api.model('combined_details', { 'id': fields.Integer(attribute='0.id', description='User的ID'), 'name': fields.String(attribute='0.name', description='User的名称'), 'token': fields.String(attribute='1.token', description='Device的Token') })
这里的'0.id'代表取元组第0个元素(User对象)的id属性,'1.token'代表取元组第1个元素(Device对象)的token属性。
修改控制器的序列化装饰器
把原来的@api.marshal_list_with(_details, envelope='data')换成:
@api.marshal_list_with(UserDto.combined_details, envelope='data')
这样序列化器会自动从元组里提取对应字段,直接生成你想要的结构。
4. 额外小提示
注意你的User模型里name字段定义成了db.Column(db.Integer),这大概率是个笔误——名称应该是字符串类型,建议修正为:
name = db.Column(db.String, unique=True, nullable=True)
调整完成后,你的API就能返回符合预期的响应了:
{ "data": [ { "id": 20, "name": null, "token": "Some String here" } ] }
(如果确实需要你期望里的双层数组,只需要在返回结果时多包一层数组,比如return [combined_results]即可)
内容的提问来源于stack exchange,提问作者ken

