将SqlAlchemy orm结果转换为dict

时间:2014-05-08 23:11:23

标签: python json sqlalchemy

如何将SQLAlchemy orm对象结果转换为JSON格式?

目前我正在使用sqlalchemy反射来反映数据库中的表。 考虑我有一个用户表和一个我正在反映数据库的地址表。 用户实体与地址实体具有一对一的关系。 下面是从DB反映表格的代码,并使用mapper类来映射关系。

from sqlalchemy import Table
from sqlalchemy.orm import mapper, relationship
user_reflection = Table('user', metadata, autoload=True, autoload_with=engine)
class User(object):
    def __init__(self, id, name, dob):
        self.id = id
        self.name = name
        self.dob = dob
address_reflection = Table('address', metadata, autoload=True, autoload_with=engine)
mapper(User,
       user_reflection,
       properties={
           'address': relationship(SourceAddress, uselist=False)
       }
)

现在,当我使用sqlalchemy orm

查询对象时
user = session.query(User).first()
user_dict = object_to_dict(user)

现在,当我想将用户对象转换为dict时 我使用以下方法

def object_to_dict(obj):
    columns = [column.key for column in class_mapper(obj.__class__).columns]
    get_key_value = lambda c: (c, getattr(obj, c).isoformat()) if isinstance(getattr(obj, c), datetime) else (c, getattr(obj, c))
    return dict(map(get_key_value, columns))

但是,object_to_dict方法工作正常,如果返回的用户对象与另一个表没有关系,则返回有效的dic对象。 如果用户对象有关系,则object_to_dict方法不会自动展开关系对象并将其转换为dict。

有人可以建议我如何自动确定返回的用户对象是否有关系,并将关系对象扩展为dict,如果它有一个,依此类推任意数量的子对象。

2 个答案:

答案 0 :(得分:10)

您可以使用映射器的relationships属性。代码选择取决于您希望如何映射数据以及关系的外观。如果您有很多递归关系,则可能需要使用max_depth计数器。我的示例使用一组关系来防止递归循环。你可以完全消除递归,如果你只打算深入一个,但你确实说“等等”。

def object_to_dict(obj, found=None):
    if found is None:
        found = set()
    mapper = class_mapper(obj.__class__)
    columns = [column.key for column in mapper.columns]
    get_key_value = lambda c: (c, getattr(obj, c).isoformat()) if isinstance(getattr(obj, c), datetime) else (c, getattr(obj, c))
    out = dict(map(get_key_value, columns))
    for name, relation in mapper.relationships.items():
        if relation not in found:
            found.add(relation)
            related_obj = getattr(obj, name)
            if related_obj is not None:
                if relation.uselist:
                    out[name] = [object_to_dict(child, found) for child in related_obj]
                else:
                    out[name] = object_to_dict(related_obj, found)
    return out

另外,请注意需要考虑的性能问题。您可能希望使用诸如joinedload或subqueryload之类的选项,以防止执行过多的SQL查询。

答案 1 :(得分:3)

尽管" doog adibies"答案已被接受,我对它提出了支持,因为它非常有用,算法中有几个值得注意的问题:

  1. 关系的子序列化在第一个孩子停止(因为过早添加到" found")
  2. 它还会序列化后退关系,在大多数情况下这些关系是不可取的(如果您的Father对象与Son的关系配置为backref,则会产生额外的关系其中每个子的Father节点,与主Father对象已提供的数据相同!)
  3. 为了解决这些问题,我定义了另一个set()来跟踪不需要的后退关系,之后我在代码中移动了对访问过的孩子的跟踪。我还故意重命名变量,以便更清楚(当然是IMO)它们代表什么以及算法如何工作,并用更清晰的字典理解取代map()

    以下是我的实际工作实现,已针对4维嵌套对象(User - > UserProject - > UserProjectEntity - > UserProjectEntityField)进行测试:

    def model_to_dict(obj, visited_children=None, back_relationships=None):
        if visited_children is None:
            visited_children = set()
        if back_relationships is None:
            back_relationships = set()
        serialized_data = {c.key: getattr(obj, c.key) for c in obj.__table__.columns}
        relationships = class_mapper(obj.__class__).relationships
        visitable_relationships = [(name, rel) for name, rel in relationships.items() if name not in back_relationships]
        for name, relation in visitable_relationships:
            if relation.backref:
                back_relationships.add(relation.backref)
            relationship_children = getattr(obj, name)
            if relationship_children is not None:
                if relation.uselist:
                    children = []
                    for child in [c for c in relationship_children if c not in visited_children]:
                        visited_children.add(child)
                        children.append(model_to_dict(child, visited_children, back_relationships))
                    serialized_data[name] = children
                else:
                    serialized_data[name] = model_to_dict(relationship_children, visited_children, back_relationships)
        return serialized_data