简体   繁体   English

将 SqlAlchemy orm 结果转换为字典

[英]Convert SqlAlchemy orm result to dict

How to convert SQLAlchemy orm object result to JSON format?如何将 SQLAlchemy orm object 结果转换为 JSON 格式?

Currently I am using sqlalchemy reflection to reflect tables from the DB.目前我正在使用 sqlalchemy 反射来反映数据库中的表。 Consider I have a User table and a Address table I am reflecting fro the DB.假设我有一个 User 表和一个 Address 表,我正在从数据库中反映出来。 The user entity has one to one relationship with the address entity.用户实体与地址实体具有一对一的关系。 Below is the code to reflect the table from the DB and use the mapper class to map the relationship.下面是从数据库反映表并使用映射器 class 到 map 关系的代码。

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)
       }
)

Now when I query the object using sqlalchemy orm现在,当我使用 sqlalchemy orm 查询 object 时

user = session.query(User).first()
user_dict = object_to_dict(user)

Now, when I want to convert the user object to dict I use the below method现在,当我想将用户 object 转换为字典时,我使用以下方法

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))

However, the object_to_dict methods works fine and returns a valid dic object if the returned user object that did not have a relationship with another table.但是,如果返回的用户 object 与另一个表没有关系,则 object_to_dict 方法可以正常工作并返回有效的 dic object。 If the user object has a relationship the object_to_dict method doesn't auto-expand relations object and convert it to dict.如果用户 object 有关系,则 object_to_dict 方法不会自动扩展关系 object 并将其转换为字典。

Could anyone suggest me how I could automatically determine if the returned user object has a relationship and expand the relationship object to a dict if it has one and so on for any number of child objects.谁能建议我如何自动确定返回的用户 object 是否有关系,并将关系 object 扩展为一个字典,如果它有一个等等对于任意数量的子对象。

You can use the relationships property of the mapper. 您可以使用映射器的relationships属性。 The code choices depend on how you want to map your data and how your relationships look. 代码选择取决于您希望如何映射数据以及关系的外观。 If you have a lot of recursive relationships, you may want to use a max_depth counter. 如果您有很多递归关系,则可能需要使用max_depth计数器。 My example below uses a set of relationships to prevent a recursive loop. 我的示例使用一组关系来防止递归循环。 You could eliminate the recursion entirely if you only plan to go down one in depth, but you did say "and so on". 你可以完全消除递归,如果你只打算深入一个,但你确实说“等等”。

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

Also, be aware that there are performance issues to consider. 另请注意,需要考虑性能问题。 You may want to use options such as joinedload or subqueryload in order to prevent executing an excessive number of SQL queries. 您可能希望使用诸如joinedload或subqueryload之类的选项,以防止执行过多的SQL查询。

Despite "doog adibies" answer has been accepted and I upvoted it since has been extremely helpful, there are a couple of notable issues in the algorithm: 尽管“doog adibies”的答案已经被接受并且我对它提出了支持,因为它非常有用,但算法中存在一些值得注意的问题:

  1. The sub-serialization of relationships stops at the first child (because of the premature addition to " found ") 关系的子序列化在第一个孩子停止(因为过早添加到“ found ”)
  2. It also serializes back relationships, which in most cases are not desiderable (if you have a Father object with a relationship to Son with a configured backref , you will generate an extra Father node for each son in it, with the same data that the main Father object already provides!) 它还序列化后退关系,在大多数情况下这些关系是backref (如果你有一个Father对象与Son配合backref的关系,你将为其中的每个儿子生成一个额外的Father节点,其中包含与主要相同的数据Father对象已经提供!)

To fix these issues, I defined another set() to track undesired back relationships and I moved the tracking of visited children later in the code. 为了解决这些问题,我定义了另一个set()来跟踪不需要的后退关系,之后我在代码中移动了对被访问孩子的跟踪。 I also deliberately renamed variables in order to make more clear (of course IMO) what they represents and how the algorithm works and replaced the map() with a cleaner dictionary comprehension. 我还故意重命名变量,以便更清楚(当然是IMO)它们代表什么以及算法如何工作,并用更清晰的字典理解取代map()

The following is my actual working implementation, which has been tested against nested objects of 4 dimensions (User -> UserProject -> UserProjectEntity -> UserProjectEntityField): 以下是我的实际工作实现,已针对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

Based on answers by "doog abides" and "daveoncode", with documentation and minor correction (as mentioned by "iuridiniz")基于“doog abides”和“daveoncode”的回答,以及文档和小的更正(如“iuridiniz”所述)

https://gist.github.com/hrishikeshrt/abb610743c394ce140196498b9c4ff0b https://gist.github.com/hrishikeshrt/abb610743c394ce140196498b9c4ff0b

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM