How to serialize SqlAlchemy result to JSON?
Django有一些从DB到JSON格式返回的ORM模型的自动序列化。
如何将SQLAlchemy查询结果序列化为JSON格式?
我尝试了
我试过
1 | TypeError: <Product('3', 'some name', 'some desc')> is not JSON serializable |
是否真的很难将SQLAlchemy ORM对象序列化为JSON / XML? 它没有默认的序列化器吗? 现在序列化ORM查询结果是非常常见的任务。
我需要的只是返回SQLAlchemy查询结果的JSON或XML数据表示。
需要在javascript datagird中使用SQLAlchemy对象的JSON / XML格式的查询结果(JQGrid http://www.trirand.com/blog/)
您可以将对象输出为dict:
1 2 3 | class User: def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} |
然后使用User.as_dict()来序列化您的对象。
如将sqlalchemy行对象转换为python dict中所述
平面实施
你可以使用这样的东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from sqlalchemy.ext.declarative import DeclarativeMeta class AlchemyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj.__class__, DeclarativeMeta): # an SQLAlchemy class fields = {} for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']: data = obj.__getattribute__(field) try: json.dumps(data) # this will fail on non-encodable values, like other classes fields[field] = data except TypeError: fields[field] = None # a json-encodable dict return fields return json.JSONEncoder.default(self, obj) |
然后使用以下命令转换为JSON:
1 2 | c = YourAlchemyClass() print json.dumps(c, cls=AlchemyEncoder) |
它将忽略不可编码的字段(将它们设置为"无")。
它不会自动扩展关系(因为这可能会导致自我引用,并永远循环)。
递归的,非循环的实现
但是,如果你宁愿循环,你可以使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from sqlalchemy.ext.declarative import DeclarativeMeta def new_alchemy_encoder(): _visited_objs = [] class AlchemyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj.__class__, DeclarativeMeta): # don't re-visit self if obj in _visited_objs: return None _visited_objs.append(obj) # an SQLAlchemy class fields = {} for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']: fields[field] = obj.__getattribute__(field) # a json-encodable dict return fields return json.JSONEncoder.default(self, obj) return AlchemyEncoder |
然后使用以下方法编码对象
1 | print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False) |
这将编码所有孩子,他们所有的孩子,以及他们所有的孩子...基本上可能编码整个数据库。当它达到之前编码的内容时,它会将其编码为"无"。
递归的,可能循环的选择性实现
另一种可能更好的替代方法是能够指定要扩展的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | def new_alchemy_encoder(revisit_self = False, fields_to_expand = []): _visited_objs = [] class AlchemyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj.__class__, DeclarativeMeta): # don't re-visit self if revisit_self: if obj in _visited_objs: return None _visited_objs.append(obj) # go through each field in this SQLalchemy class fields = {} for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']: val = obj.__getattribute__(field) # is this field another SQLalchemy object, or a list of SQLalchemy objects? if isinstance(val.__class__, DeclarativeMeta) or (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)): # unless we're expanding this field, stop here if field not in fields_to_expand: # not expanding this field: set it to None and continue fields[field] = None continue fields[field] = val # a json-encodable dict return fields return json.JSONEncoder.default(self, obj) return AlchemyEncoder |
你现在可以用以下方式调用它:
1 | print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False) |
例如,仅扩展名为"parents"的SQLAlchemy字段。
你可以将RowProxy转换为这样的dict:
1 | d = dict(row.items()) |
然后将其序列化为JSON(您必须为
如果您只想要一条记录(而不是相关记录的完整层次结构),那并不难。
1 | json.dumps([(dict(row.items())) for row in rs]) |
我建议使用最近浮出水面的图书馆棉花糖。它允许您创建序列化程序来表示模型实例,并支持关系和嵌套对象。
看看他们的SQLAlchemy示例。
Flask-JsonTools包为您的模型提供了JsonSerializableBase Base类的实现。
用法:
1 2 3 4 5 6 7 | from sqlalchemy.ext.declarative import declarative_base from flask.ext.jsontools import JsonSerializableBase Base = declarative_base(cls=(JsonSerializableBase,)) class User(Base): #... |
现在
如果你的框架不是Flask,你可以抓住代码
出于安全原因,您永远不应该返回所有模型的字段。我更愿意有选择地选择它们。
Flask的json编码现在支持UUID,日期时间和关系(并为flask_sqlalchemy
app/json_encoder.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from sqlalchemy.ext.declarative import DeclarativeMeta from flask import json class AlchemyEncoder(json.JSONEncoder): def default(self, o): if isinstance(o.__class__, DeclarativeMeta): data = {} fields = o.__json__() if hasattr(o, '__json__') else dir(o) for field in [f for f in fields if not f.startswith('_') and f not in ['metadata', 'query', 'query_class']]: value = o.__getattribute__(field) try: json.dumps(value) data[field] = value except TypeError: data[field] = None return data return json.JSONEncoder.default(self, o) |
app/__init__.py
1 2 3 | # json encoding from app.json_encoder import AlchemyEncoder app.json_encoder = AlchemyEncoder |
有了这个,我可以选择添加一个
app/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Queue(db.Model): id = db.Column(db.Integer, primary_key=True) song_id = db.Column(db.Integer, db.ForeignKey('song.id'), unique=True, nullable=False) song = db.relationship('Song', lazy='joined') type = db.Column(db.String(20), server_default=u'audio/mpeg') src = db.Column(db.String(255), nullable=False) created_at = db.Column(db.DateTime, server_default=db.func.now()) updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) def __init__(self, song): self.song = song self.src = song.full_path def __json__(self): return ['song', 'src', 'type', 'created_at'] |
我将@jsonapi添加到我的视图中,返回结果列表,然后我的输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [ { "created_at":"Thu, 23 Jul 2015 11:36:53 GMT", "song": { "full_path":"/static/music/Audioslave/Audioslave [2002]/1 Cochise.mp3", "id": 2, "path_name":"Audioslave/Audioslave [2002]/1 Cochise.mp3" }, "src":"/static/music/Audioslave/Audioslave [2002]/1 Cochise.mp3", "type":"audio/mpeg" } ] |
您可以使用SqlAlchemy的内省作为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | mysql = SQLAlchemy() from sqlalchemy import inspect class Contacts(mysql.Model): __tablename__ = 'CONTACTS' id = mysql.Column(mysql.Integer, primary_key=True) first_name = mysql.Column(mysql.String(128), nullable=False) last_name = mysql.Column(mysql.String(128), nullable=False) phone = mysql.Column(mysql.String(128), nullable=False) email = mysql.Column(mysql.String(128), nullable=False) street = mysql.Column(mysql.String(128), nullable=False) zip_code = mysql.Column(mysql.String(128), nullable=False) city = mysql.Column(mysql.String(128), nullable=False) def toDict(self): return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs } @app.route('/contacts',methods=['GET']) def getContacts(): contacts = Contacts.query.all() contactsArr = [] for contact in contacts: contactsArr.append(contact.toDict()) return jsonify(contactsArr) @app.route('/contacts/<int:id>',methods=['GET']) def getContact(id): contact = Contacts.query.get(id) return jsonify(contact.toDict()) |
从这里的答案中获得灵感:
将sqlalchemy行对象转换为python dict
更详细的解释。
在您的模型中,添加:
1 2 | def as_dict(self): return {c.name: str(getattr(self, c.name)) for c in self.__table__.columns} |
您现在可以像这样查询数据库
1 | some_result = User.query.filter_by(id=current_user.id).first().as_dict() |
需要
1 | jsonify(some_result) |
它不是那么直截了当。我写了一些代码来做到这一点。我还在努力,它使用MochiKit框架。它基本上使用代理和注册的JSON转换器在Python和Javascript之间转换复合对象。
数据库对象的浏览器端是db.js
它需要proxy.js中的基本Python代理源。
在Python端有基本代理模块。
最后是webserver.py中的SqlAlchemy对象编码器。
它还取决于models.py文件中的元数据提取器。
这是一个解决方案,可让您根据自己的意愿选择要包含在输出中的关系。
注意:这是一个完整的重写,将dict / str作为arg而不是列表。修复了一些东西......
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | def deep_dict(self, relations={}): """Output a dict of an SA object recursing as deep as you want. Takes one argument, relations which is a dictionary of relations we'd like to pull out. The relations dict items can be a single relation name or deeper relation names connected by sub dicts Example: Say we have a Person object with a family relationship person.deep_dict(relations={'family':None}) Say the family object has homes as a relation then we can do person.deep_dict(relations={'family':{'homes':None}}) OR person.deep_dict(relations={'family':'homes'}) Say homes has a relation like rooms you can do person.deep_dict(relations={'family':{'homes':'rooms'}}) and so on... """ mydict = dict((c, str(a)) for c, a in self.__dict__.items() if c != '_sa_instance_state') if not relations: # just return ourselves return mydict # otherwise we need to go deeper if not isinstance(relations, dict) and not isinstance(relations, str): raise Exception("relations should be a dict, it is of type {}".format(type(relations))) # got here so check and handle if we were passed a dict if isinstance(relations, dict): # we were passed deeper info for left, right in relations.items(): myrel = getattr(self, left) if isinstance(myrel, list): mydict[left] = [rel.deep_dict(relations=right) for rel in myrel] else: mydict[left] = myrel.deep_dict(relations=right) # if we get here check and handle if we were passed a string elif isinstance(relations, str): # passed a single item myrel = getattr(self, relations) left = relations if isinstance(myrel, list): mydict[left] = [rel.deep_dict(relations=None) for rel in myrel] else: mydict[left] = myrel.deep_dict(relations=None) return mydict |
所以对于一个使用人/家庭/家庭/房间的例子......把它变成json所有你需要的是
1 | json.dumps(person.deep_dict(relations={'family':{'homes':'rooms'}})) |
虽然最初的问题可以追溯到一段时间,但这里的答案数量(以及我自己的经验)表明,这是一个非常重要的问题,有许多不同的方法,通过不同的权衡来改变复杂性。
这就是为什么我构建了SQLAthanor库,它扩展了SQLAlchemy的声明性ORM,并提供了可配置的序列化/反序列化支持,您可能需要查看它。
该库支持:
- Python 2.7,3.4,3.5和3.6。
- SQLAlchemy版本0.9及更高版本
-
与JSON,CSV,YAML和Python
dict 的序列化/反序列化 - 列/属性,关系,混合属性和关联代理的序列化/反序列化
-
为特定格式和列/关系/属性启用和禁用序列化(例如,您希望支持入站
password 值,但从不包含出站值) - 预序列化和反序列化后的值处理(用于验证或类型强制)
- 一个非常直接的语法,它既是Pythonic又与SQLAlchemy自己的方法无缝对应
你可以在这里查看(我希望!)综合文档:https://sqlathanor.readthedocs.io/en/latest
希望这可以帮助!
自定义序列化和反序列化。
"from_json"(类方法)基于json数据构建Model对象。
"deserialize"只能在实例上调用,并将所有数据从json合并到Model实例中。
"序列化" - 递归序列化
需要__write_only__属性来定义只写属性(例如"password_hash")。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | class Serializable(object): __exclude__ = ('id',) __include__ = () __write_only__ = () @classmethod def from_json(cls, json, selfObj=None): if selfObj is None: self = cls() else: self = selfObj exclude = (cls.__exclude__ or ()) + Serializable.__exclude__ include = cls.__include__ or () if json: for prop, value in json.iteritems(): # ignore all non user data, e.g. only if (not (prop in exclude) | (prop in include)) and isinstance( getattr(cls, prop, None), QueryableAttribute): setattr(self, prop, value) return self def deserialize(self, json): if not json: return None return self.__class__.from_json(json, selfObj=self) @classmethod def serialize_list(cls, object_list=[]): output = [] for li in object_list: if isinstance(li, Serializable): output.append(li.serialize()) else: output.append(li) return output def serialize(self, **kwargs): # init write only props if len(getattr(self.__class__, '__write_only__', ())) == 0: self.__class__.__write_only__ = () dictionary = {} expand = kwargs.get('expand', ()) or () prop = 'props' if expand: # expand all the fields for key in expand: getattr(self, key) iterable = self.__dict__.items() is_custom_property_set = False # include only properties passed as parameter if (prop in kwargs) and (kwargs.get(prop, None) is not None): is_custom_property_set = True iterable = kwargs.get(prop, None) # loop trough all accessible properties for key in iterable: accessor = key if isinstance(key, tuple): accessor = key[0] if not (accessor in self.__class__.__write_only__) and not accessor.startswith('_'): # force select from db to be able get relationships if is_custom_property_set: getattr(self, accessor, None) if isinstance(self.__dict__.get(accessor), list): dictionary[accessor] = self.__class__.serialize_list(object_list=self.__dict__.get(accessor)) # check if those properties are read only elif isinstance(self.__dict__.get(accessor), Serializable): dictionary[accessor] = self.__dict__.get(accessor).serialize() else: dictionary[accessor] = self.__dict__.get(accessor) return dictionary |
在SQLAlchemy中使用内置的序列化程序:
1 2 3 4 5 6 7 | from sqlalchemy.ext.serializer import loads, dumps obj = MyAlchemyObject() # serialize object serialized_obj = dumps(obj) # deserialize object obj = loads(serialized_obj) |
如果要在会话之间传输对象,请记住使用
要再次附加它,只需执行
1 2 | def alc2json(row): return dict([(col, str(getattr(row,col))) for col in row.__table__.columns.keys()]) |
我以为我会用这个打高尔夫球。
仅供参考:我使用的是automap_base,因为我们根据业务需求提供了单独设计的模式。我今天刚开始使用SQLAlchemy但是文档声明automap_base是declarative_base的扩展,它似乎是SQLAlchemy ORM中的典型范例,所以我相信这应该有效。
根据Tjorriemorrie的解决方案,它不会使用以下外键,但它只是将列与值匹配并通过str()处理Python类型 - 列值。我们的值包括Python datetime.time和decimal.Decimal类类型的结果,因此它可以完成工作。
希望这可以帮助任何路人!
以下代码将sqlalchemy结果序列化为json。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import json from collections import OrderedDict def asdict(self): result = OrderedDict() for key in self.__mapper__.c.keys(): if getattr(self, key) is not None: result[key] = str(getattr(self, key)) else: result[key] = getattr(self, key) return result def to_array(all_vendors): v = [ ven.asdict() for ven in all_vendors ] return json.dumps(v) |
叫声有趣,
1 2 3 | def all_products(): all_products = Products.query.all() return to_array(all_products) |
AlchemyEncoder很棒,但有时会失败并带有Decimal值。这是一个改进的编码器,可以解决小数问题 -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class AlchemyEncoder(json.JSONEncoder): # To serialize SQLalchemy objects def default(self, obj): if isinstance(obj.__class__, DeclarativeMeta): model_fields = {} for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']: data = obj.__getattribute__(field) print data try: json.dumps(data) # this will fail on non-encodable values, like other classes model_fields[field] = data except TypeError: model_fields[field] = None return model_fields if isinstance(obj, Decimal): return float(obj) return json.JSONEncoder.default(self, obj) |
我知道这是一个相当老的帖子。我接受了@SashaB给出的解决方案并根据我的需要进行了修改。
我添加了以下内容:
我的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | def alchemy_json_encoder(revisit_self = False, fields_to_expand = [], fields_to_ignore = [], fields_to_replace = {}): """ Serialize SQLAlchemy result into JSon :param revisit_self: True / False :param fields_to_expand: Fields which are to be expanded for including their children and all :param fields_to_ignore: Fields to be ignored while encoding :param fields_to_replace: Field keys to be replaced by values assigned in dictionary :return: Json serialized SQLAlchemy object """ _visited_objs = [] class AlchemyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj.__class__, DeclarativeMeta): # don't re-visit self if revisit_self: if obj in _visited_objs: return None _visited_objs.append(obj) # go through each field in this SQLalchemy class fields = {} for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and x not in fields_to_ignore]: val = obj.__getattribute__(field) # is this field method defination, or an SQLalchemy object if not hasattr(val,"__call__") and not isinstance(val, BaseQuery): field_name = fields_to_replace[field] if field in fields_to_replace else field # is this field another SQLalchemy object, or a list of SQLalchemy objects? if isinstance(val.__class__, DeclarativeMeta) or \ (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)): # unless we're expanding this field, stop here if field not in fields_to_expand: # not expanding this field: set it to None and continue fields[field_name] = None continue fields[field_name] = val # a json-encodable dict return fields return json.JSONEncoder.default(self, obj) return AlchemyEncoder |
希望它可以帮到某人!
在Flask下,这可以处理和处理数据时间字段,转换类型的字段
1 2 3 4 5 6 7 | obj = {c.name: str(getattr(self, c.name)) for c in self.__table__.columns} # This to get the JSON body return json.dumps(obj) # Or this to get a response object return jsonify(obj) |
带有utf-8的内置串行器扼流圈无法解码某些输入的无效起始字节。相反,我去了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | def row_to_dict(row): temp = row.__dict__ temp.pop('_sa_instance_state', None) return temp def rows_to_list(rows): ret_rows = [] for row in rows: ret_rows.append(row_to_dict(row)) return ret_rows @website_blueprint.route('/api/v1/some/endpoint', methods=['GET']) def some_api(): ''' /some_endpoint ''' rows = rows_to_list(SomeModel.query.all()) response = app.response_class( response=jsonplus.dumps(rows), status=200, mimetype='application/json' ) return response |
我利用(太多?)词典:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def serialize(_query): #d = dictionary written to per row #D = dictionary d is written to each time, then reset #Master = dictionary of dictionaries; the id Key (int, unique from database) from D is used as the Key for the dictionary D entry in Master Master = {} D = {} x = 0 for u in _query: d = u.__dict__ D = {} for n in d.keys(): if n != '_sa_instance_state': D[n] = d[n] x = d['id'] Master[x] = D return Master |
使用flask(包括jsonify)和flask_sqlalchemy运行以打印输出为JSON。
使用jsonify(serialize())调用该函数。
适用于我迄今为止尝试的所有SQLAlchemy查询(运行SQLite3)