You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Sqlalchemy DataClass模式下避免最大递归深度超出错误

解决SQLAlchemy Dataclass双向关系JSON序列化无限递归问题

问题描述

将SQLAlchemy类映射为dataclass后,在Flask中导出JSON时,Child类会自动作为子列表在Parent类中导出,十分实用。但当想在Child类中显示父对象字段时,会触发maximum recursion depth exceeded无限递归错误。

疑问:是否可以在创建SQLAlchemy relationship时指定要选择的列?或者有没有其他方式自定义SQLAlchemy-dataclass对象的数据导出?

错误代码示例:

@reg.mapped_as_dataclass
class Parent:
    __tablename__ = 'parent'

    Id: int = db.Column('id', db.Integer, primary_key=True)
    Name: str = db.Column('name', db.String)
    
    ChildrenList: 'Child' = db.relationship('Child', default_factory=list, back_populates='ParentObject')


@reg.mapped_as_dataclass
class Child:
    __tablename__ = 'child'

    Id: int = db.Column('id', db.Integer, primary_key=True)
    ParentId: int = db.Column('parent$id', db.Integer, ForeignKey('parent.id'))
    
    # 该字段引发无限递归
    ParentObject: 'Parent' = db.relationship('Parent', back_populates='ChildrenList')
    
    # 期望实现的效果(SQLAlchemy原生不支持这类参数)
    # ParentObject: 'Parent' = db.relationship('Parent', back_populates='ChildrenList', select_fields='Id, Name')
    # ParentObject: 'Parent' = db.relationship('Parent', back_populates='ChildrenList', exclude_fields='ChildrenList')


def json_return_example():
    return db.session.query(Parent).all()

解决方案

方案1:自定义JSON编码器控制序列化字段

通过扩展Flask的JSON编码器,在序列化时跳过递归字段,或仅保留父对象的指定属性:

from flask import Flask, jsonify
import json

app = Flask(__name__)

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, '__dataclass_fields__'):
            data = {}
            for field in obj.__dataclass_fields__:
                if isinstance(obj, Child) and field == 'ParentObject':
                    # 仅序列化父对象的Id和Name
                    data['ParentObject'] = {'Id': obj.ParentObject.Id, 'Name': obj.ParentObject.Name}
                elif not (isinstance(obj, Parent) and field == 'ChildrenList'):
                    data[field] = getattr(obj, field)
            return data
        return super().default(obj)

app.json_encoder = CustomJSONEncoder

# 接口示例
@app.route('/parents')
def get_parents():
    parents = db.session.query(Parent).all()
    return jsonify(parents)

方案2:用Hybrid Property返回父对象部分字段

将完整的双向关系隐藏为内部字段,通过混合属性返回需要的父对象数据,避免递归:

from sqlalchemy.ext.hybrid import hybrid_property

@reg.mapped_as_dataclass
class Child:
    __tablename__ = 'child'

    Id: int = db.Column('id', db.Integer, primary_key=True)
    ParentId: int = db.Column('parent$id', db.Integer, ForeignKey('parent.id'))
    
    # 内部关系字段,仅用于查询,不参与序列化
    _ParentObject: 'Parent' = db.relationship('Parent', back_populates='ChildrenList')
    
    # 对外暴露的父对象数据,仅返回指定字段
    @hybrid_property
    def ParentObject(self):
        return {'Id': self._ParentObject.Id, 'Name': self._ParentObject.Name}

# Parent类需对应修改关系的back_populates
@reg.mapped_as_dataclass
class Parent:
    __tablename__ = 'parent'

    Id: int = db.Column('id', db.Integer, primary_key=True)
    Name: str = db.Column('name', db.String)
    
    ChildrenList: 'Child' = db.relationship('Child', default_factory=list, back_populates='_ParentObject')

方案3:使用Marshmallow定义序列化Schema

通过Marshmallow可以精确控制序列化字段,彻底规避递归问题:
首先安装依赖:pip install marshmallow

然后定义Schema:

from marshmallow import Schema, fields

class ChildSchema(Schema):
    Id = fields.Int()
    ParentId = fields.Int()
    # 仅序列化父对象的指定字段
    ParentObject = fields.Nested('ParentSchema', only=['Id', 'Name'])

class ParentSchema(Schema):
    Id = fields.Int()
    Name = fields.Str()
    ChildrenList = fields.Nested(ChildSchema, many=True)

# 接口示例
@app.route('/parents')
def get_parents():
    parents = db.session.query(Parent).all()
    schema = ParentSchema(many=True)
    return jsonify(schema.dump(parents))

补充说明

SQLAlchemy的relationship本身没有select_fields或exclude_fields这类参数,因为关系的核心作用是建立对象关联,而非直接控制序列化逻辑。解决递归问题的关键是在序列化阶段精准控制输出内容,或通过混合属性间接返回需要的父对象数据,避免加载完整的双向关联对象。

内容的提问来源于stack exchange,提问作者tehniss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 11:26:30