关于python:jsonify在Flask中的SQLAlchemy结果集

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的日期时间都失败了;)

我过去所做的是为需要序列化的类添加一个额外的属性(如serialize)。

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]

现在所需要的只是使用Serializer mixin类扩展SQLAlchemy模型。

如果存在您不希望公开的字段,或者需要特殊格式化的字段,只需覆盖模型子类中的serialize()函数即可。

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

在您的控制器中,您所要做的就是在结果上调用serialize()函数(或serialize_list(l),如果查询结果列表中):

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模型有点像dict

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

这就是能够将MyModel()的实例传递给dict()并从中获取真实的dict实例所需的全部内容,这使我们在使jsonify()理解它时有很长的路要走。接下来,我们需要扩展JSONEncoder以使我们完成其余的工作:

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输出包含实际上未存储在数据库中的字段),那也很容易。只需将计算字段定义为@property s,并像这样扩展keys()方法:

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


如果您使用的是flask-restful,则可以使用marshal:

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。序列化很容易处理(不需要jsonify),日期也不是问题。请注意,uri字段的内容是根据topic端点和id自动生成的。


对于平面查询(无连接),您可以执行此操作

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有点不同)。

在我的models.py文件中:

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

在我的观点中,无论我在哪里调用Jsonify,我都会调用SWJsonify,如下所示:

1
2
3
4
@app.route('/posts')
def posts():
  posts = Post.query.limit(PER_PAGE).all()
  return SWJsonify({'posts':posts })

似乎工作得很好。甚至在关系上。我没有得到它,所以YMMV,但到目前为止,它对我来说感觉非常"正确"。

建议欢迎。


flask-restful 0.3.6 Request Parsing推荐使用marshmallow

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

我知道这不是一个完美的解决方案,也不像其他人那样优雅,但对于那些想要快速修复的人来说,他们可能会尝试这个。