jsonify a SQLAlchemy result set in Flask
我正在尝试在Flask / Python中jsonify一个SQLAlchemy结果集。
Flask邮件列表建议使用以下方法http://librelist.com/browser//flask/2011/2/16/jsonify-sqlalchemy-pagination-collection-result/#04a0754b63387f87e59dda564bde426e:
1 | return jsonify(json_list = qryresult) |
但是我收到以下错误:
1 2 | TypeError: <flaskext.sqlalchemy.BaseQuery object at 0x102c2df90> is not JSON serializable |
我在这里俯瞰什么?
我发现了这个问题:如何将SqlAlchemy结果序列化为JSON? 这看起来非常相似但是我不知道Flask是否有一些魔力使其更容易,如邮件列表帖子所示。
编辑:为了澄清,这就是我的模型的样子
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 | class Rating(db.Model): __tablename__ = 'rating' id = db.Column(db.Integer, primary_key=True) fullurl = db.Column(db.String()) url = db.Column(db.String()) comments = db.Column(db.Text) overall = db.Column(db.Integer) shipping = db.Column(db.Integer) cost = db.Column(db.Integer) honesty = db.Column(db.Integer) communication = db.Column(db.Integer) name = db.Column(db.String()) ipaddr = db.Column(db.String()) date = db.Column(db.String()) def __init__(self, fullurl, url, comments, overall, shipping, cost, honesty, communication, name, ipaddr, date): self.fullurl = fullurl self.url = url self.comments = comments self.overall = overall self.shipping = shipping self.cost = cost self.honesty = honesty self.communication = communication self.name = name self.ipaddr = ipaddr self.date = date |
看来你实际上还没有执行你的查询。试试以下:
1 | return jsonify(json_list = qryresult.all()) |
[编辑]:jsonify问题是,通常对象不能自动进行jsonified。甚至Python的日期时间都失败了;)
我过去所做的是为需要序列化的类添加一个额外的属性(如
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 | def dump_datetime(value): """Deserialize datetime object into string form for JSON processing.""" if value is None: return None return [value.strftime("%Y-%m-%d"), value.strftime("%H:%M:%S")] class Foo(db.Model): # ... SQLAlchemy defs here.. def __init__(self, ...): # self.foo = ... pass @property def serialize(self): """Return object data in easily serializable format""" return { 'id' : self.id, 'modified_at': dump_datetime(self.modified_at), # This is an example how to deal with Many2Many relations 'many2many' : self.serialize_many2many } @property def serialize_many2many(self): """ Return object's relations in easily serializable format. NB! Calls many2many's serialize property. """ return [ item.serialize for item in self.many2many] |
现在我可以做的观点:
1 | return jsonify(json_list=[i.serialize for i in qryresult.all()]) |
希望这可以帮助 ;)
[编辑2019]:
如果您有更复杂的对象或循环引用,请使用像marshmallow这样的库。
我有同样的需要,序列化为json。看看这个问题。它显示了如何以编程方式发现列。所以,从那以后我创建了下面的代码。它适用于我,我将在我的网络应用程序中使用它。快乐的编码!
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 | def to_json(inst, cls): """ Jsonify the sql alchemy query result. """ convert = dict() # add your coversions for things like datetime's # and what-not that aren't serializable. d = dict() for c in cls.__table__.columns: v = getattr(inst, c.name) if c.type in convert.keys() and v is not None: try: d[c.name] = convert[c.type](v) except: d[c.name] ="Error: Failed to covert using", str(convert[c.type]) elif v is None: d[c.name] = str() else: d[c.name] = v return json.dumps(d) class Person(base): __tablename__ = 'person' id = Column(Integer, Sequence('person_id_seq'), primary_key=True) first_name = Column(Text) last_name = Column(Text) email = Column(Text) @property def json(self): return to_json(self, self.__class__) |
这通常对我来说足够了:
我创建了一个序列化mixin,我和我的模型一起使用。序列化函数基本上获取SQLAlchemy检查器公开的任何属性并将其放入dict中。
1 2 3 4 5 6 7 8 9 10 | from sqlalchemy.inspection import inspect class Serializer(object): def serialize(self): return {c: getattr(self, c) for c in inspect(self).attrs.keys()} @staticmethod def serialize_list(l): return [m.serialize() for m in l] |
现在所需要的只是使用
如果存在您不希望公开的字段,或者需要特殊格式化的字段,只需覆盖模型子类中的
1 2 3 4 5 6 7 8 9 10 11 | class User(db.Model, Serializer): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String) password = db.Column(db.String) # ... def serialize(self): d = Serializer.serialize(self) del d['password'] return d |
在您的控制器中,您所要做的就是在结果上调用
1 2 3 4 5 6 7 | def get_user(id): user = User.query.get(id) return json.dumps(user.serialize()) def get_users(): users = User.query.all() return json.dumps(User.serialize_list(users)) |
这是我的方法:
https://github.com/n0nSmoker/SQLAlchemy-serializer
pip安装SQLAlchemy-serializer
您可以轻松地将mixin添加到模型中,而不仅仅是调用
它的实例上的.to_dict()方法
您也可以在SerializerMixin的基础上编写自己的mixin
好的,我已经在这个工作了几个小时,我已经开发出了我认为最狡猾的解决方案。下面的代码片段是python3,但如果你需要的话,反向移植不应该太麻烦。
我们要做的第一件事是从一个mixin开始,让你的db模型有点像
1 2 3 4 5 6 7 8 9 10 11 12 | from sqlalchemy.inspection import inspect class ModelMixin: """Provide dict-like interface to db.Model subclasses.""" def __getitem__(self, key): """Expose object attributes like dict values.""" return getattr(self, key) def keys(self): """Identify what db columns we have.""" return inspect(self).attrs.keys() |
现在我们要定义我们的模型,继承mixin:
1 2 3 4 5 | class MyModel(db.Model, ModelMixin): id = db.Column(db.Integer, primary_key=True) foo = db.Column(...) bar = db.Column(...) # etc ... |
这就是能够将
1 2 3 4 5 6 7 8 9 10 11 | from flask.json import JSONEncoder from contextlib import suppress class MyJSONEncoder(JSONEncoder): def default(self, obj): # Optional: convert datetime objects to ISO format with suppress(AttributeError): return obj.isoformat() return dict(obj) app.json_encoder = MyJSONEncoder |
加分点:如果您的模型包含计算字段(也就是说,您希望JSON输出包含实际上未存储在数据库中的字段),那也很容易。只需将计算字段定义为
1 2 3 4 5 6 7 8 9 10 11 | class MyModel(db.Model, ModelMixin): id = db.Column(db.Integer, primary_key=True) foo = db.Column(...) bar = db.Column(...) @property def computed_field(self): return 'this value did not come from the db' def keys(self): return super().keys() + ['computed_field'] |
现在jsonify是微不足道的:
1 2 3 | @app.route('/whatever', methods=['GET']) def whatever(): return jsonify(dict(results=MyModel.query.all())) |
如果您使用的是
1 2 3 4 5 6 7 8 9 10 11 12 13 | from flask.ext.restful import Resource, fields, marshal topic_fields = { 'title': fields.String, 'content': fields.String, 'uri': fields.Url('topic'), 'creator': fields.String, 'created': fields.DateTime(dt_format='rfc822') } class TopicListApi(Resource): def get(self): return {'topics': [marshal(topic, topic_fields) for topic in DbTopic.query.all()]} |
您需要明确列出您要返回的内容以及它的类型,我更喜欢api。序列化很容易处理(不需要
对于平面查询(无连接),您可以执行此操作
1 2 3 4 5 | @app.route('/results/') def results(): data = Table.query.all() result = [d.__dict__ for d in data] return jsonify(result=result) |
如果您只想从数据库中返回某些列,则可以执行此操作
1 2 3 4 5 6 | @app.route('/results/') def results(): cols = ['id', 'url', 'shipping'] data = Table.query.all() result = [{col: getattr(d, col) for col in cols} for d in data] return jsonify(result=result) |
我一直在寻找这个问题的一天中的大部分时间,这就是我提出的问题(归功于https://stackoverflow.com/a/5249214/196358,我指的是这个方向)。
(注意:我正在使用flask-sqlalchemy,所以我的模型声明格式与直接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 | import json class Serializer(object): __public__ = None "Must be implemented by implementors" def to_serializable_dict(self): dict = {} for public_key in self.__public__: value = getattr(self, public_key) if value: dict[public_key] = value return dict class SWEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Serializer): return obj.to_serializable_dict() if isinstance(obj, (datetime)): return obj.isoformat() return json.JSONEncoder.default(self, obj) def SWJsonify(*args, **kwargs): return current_app.response_class(json.dumps(dict(*args, **kwargs), cls=SWEncoder, indent=None if request.is_xhr else 2), mimetype='application/json') # stolen from https://github.com/mitsuhiko/flask/blob/master/flask/helpers.py |
我所有的模型对象都是这样的:
1 2 3 | class User(db.Model, Serializer): __public__ = ['id','username'] ... field definitions ... |
在我的观点中,无论我在哪里调用
1 2 3 4 | @app.route('/posts') def posts(): posts = Post.query.limit(PER_PAGE).all() return SWJsonify({'posts':posts }) |
似乎工作得很好。甚至在关系上。我没有得到它,所以YMMV,但到目前为止,它对我来说感觉非常"正确"。
建议欢迎。
marshmallow is an ORM/ODM/framework-agnostic library for converting
complex datatypes, such as objects, to and from native Python
datatypes.
下面是一个简单的棉花糖示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from marshmallow import Schema, fields class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() from marshmallow import pprint user = User(name="Monty", email="[email protected]") schema = UserSchema() result = schema.dump(user) pprint(result) # {"name":"Monty", # "email":"[email protected]", # "created_at":"2014-08-17T14:54:16.049594+00:00"} |
核心功能包含
Declaring Schemas
Serializing Objects ("Dumping")
Deserializing Objects ("Loading")
Handling Collections of Objects
Validation
Specifying Attribute Names
Specifying Serialization/Deserialization Keys
Refactoring: Implicit Field Creation
Ordering Output
"Read-only" and"Write-only" Fields
Specify Default Serialization/Deserialization Values
Nesting Schemas
Custom Fields
如果您使用声明性基础(在已发布的一些答案的帮助下),这是我的答案:
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 | # in your models definition where you define and extend declarative_base() from sqlalchemy.ext.declarative import declarative_base ... Base = declarative_base() Base.query = db_session.query_property() ... # define a new class (call"Model" or whatever) with an as_dict() method defined class Model(): def as_dict(self): return { c.name: getattr(self, c.name) for c in self.__table__.columns } # and extend both the Base and Model class in your model definition, e.g. class Rating(Base, Model): ____tablename__ = 'rating' id = db.Column(db.Integer, primary_key=True) fullurl = db.Column(db.String()) url = db.Column(db.String()) comments = db.Column(db.Text) ... # then after you query and have a resultset (rs) of ratings rs = Rating.query.all() # you can jsonify it with s = json.dumps([r.as_dict() for r in rs], default=alchemyencoder) print (s) # or if you have a single row r = Rating.query.first() # you can jsonify it with s = json.dumps(r.as_dict(), default=alchemyencoder) # you will need this alchemyencoder where your are calling json.dumps to handle datetime and decimal format # credit to Joonas @ http://codeandlife.com/2014/12/07/sqlalchemy-results-to-json-the-easy-way/ def alchemyencoder(obj): """JSON encoder function for SQLAlchemy special classes.""" if isinstance(obj, datetime.date): return obj.isoformat() elif isinstance(obj, decimal.Decimal): return float(obj) |
我正在寻找像ActiveRecord to_json中使用的rails方法之类的东西,并且在对其他建议不满意之后使用这个Mixin实现类似的东西。它处理嵌套模型,包括或排除顶级或嵌套模型的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Serializer(object): def serialize(self, include={}, exclude=[], only=[]): serialized = {} for key in inspect(self).attrs.keys(): to_be_serialized = True value = getattr(self, key) if key in exclude or (only and key not in only): to_be_serialized = False elif isinstance(value, BaseQuery): to_be_serialized = False if key in include: to_be_serialized = True nested_params = include.get(key, {}) value = [i.serialize(**nested_params) for i in value] if to_be_serialized: serialized[key] = value return serialized |
然后,为了获得BaseQuery可序列化,我扩展了BaseQuery
1 2 3 4 | class SerializableBaseQuery(BaseQuery): def serialize(self, include={}, exclude=[], only=[]): return [m.serialize(include, exclude, only) for m in self] |
适用于以下型号
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 | class ContactInfo(db.Model, Serializer): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) full_name = db.Column(db.String()) source = db.Column(db.String()) source_id = db.Column(db.String()) email_addresses = db.relationship('EmailAddress', backref='contact_info', lazy='dynamic') phone_numbers = db.relationship('PhoneNumber', backref='contact_info', lazy='dynamic') class EmailAddress(db.Model, Serializer): id = db.Column(db.Integer, primary_key=True) email_address = db.Column(db.String()) type = db.Column(db.String()) contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id')) class PhoneNumber(db.Model, Serializer): id = db.Column(db.Integer, primary_key=True) phone_number = db.Column(db.String()) type = db.Column(db.String()) contact_info_id = db.Column(db.Integer, db.ForeignKey('contact_info.id')) phone_numbers = db.relationship('Invite', backref='phone_number', lazy='dynamic') |
你可以做点什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @app.route("/contact/search", methods=['GET']) def contact_search(): contact_name = request.args.get("name") matching_contacts = ContactInfo.query.filter(ContactInfo.full_name.like("%{}%".format(contact_name))) serialized_contact_info = matching_contacts.serialize( include={ "phone_numbers" : { "exclude" : ["contact_info","contact_info_id"] }, "email_addresses" : { "exclude" : ["contact_info","contact_info_id"] } } ) return jsonify(serialized_contact_info) |
这是一种在每个类上添加as_dict()方法的方法,以及您希望在每个类上使用的任何其他方法。
不确定这是否是理想的方式,但它有效......
1 2 3 4 5 6 7 8 | class Base(object): def as_dict(self): return dict((c.name, getattr(self, c.name)) for c in self.__table__.columns) Base = declarative_base(cls=Base) |
我只是想添加我的方法来做到这一点。
只需定义一个客户json编码器来对您的数据库模型进行serilize。
1 2 3 4 5 6 7 8 9 10 11 | class ParentEncoder(json.JSONEncoder): def default(self, obj): # convert object to a dict d = {} if isinstance(obj, Parent): return {"id": obj.id,"name": obj.name, 'children': list(obj.child)} if isinstance(obj, Child): return {"id": obj.id,"name": obj.name} d.update(obj.__dict__) return d |
然后在你的视图功能
1 2 3 4 | parents = Parent.query.all() dat = json.dumps({"data": parents}, cls=ParentEncoder) resp = Response(response=dat, status=200, mimetype="application/json") return (resp) |
尽管父母有关系,但它运作良好
我正在使用名为jobDict的RowProxy对象列表的sql查询defaultdict
我花了一段时间才弄清楚对象的类型。
这是一个非常简单快速的方法来解决一些干净的jsonEncoding,只需将行类型转换为列表并最初定义一个值为list的dict。
1 2 3 4 5 6 7 8 9 | jobDict = defaultdict(list) def set_default(obj): # trickyness needed here via import to know type if isinstance(obj, RowProxy): return list(obj) raise TypeError jsonEncoded = json.dumps(jobDict, default=set_default) |
它已经很多次了,并且有很多有效的答案,但以下代码块似乎有效:
1 2 3 4 | my_object = SqlAlchemyModel() my_serializable_obj = my_object.__dict__ del my_serializable_obj["_sa_instance_state"] print(jsonify(my_serializable_object)) |
我知道这不是一个完美的解决方案,也不像其他人那样优雅,但对于那些想要快速修复的人来说,他们可能会尝试这个。