jsonify набор результатов SQLAlchemy во Flask

Я пытаюсь jsonify набор результатов SQLAlchemy во Flask / Python.

В списке рассылки Flask был предложен следующий метод http://librelist.com/browser//flask/2011/2/16/jsonify-sqlalchemy-pagination-collection-result/#04a0754b63387f87e59dda564bde426e:

return jsonify(json_list = qryresult)

Однако я получаю следующую ошибку:

TypeError: <flaskext.sqlalchemy.BaseQuery object at 0x102c2df90> 
is not JSON serializable

Что я здесь упускаю?

Я нашел этот вопрос: Как сериализовать результат SqlAlchemy в JSON? что кажется очень похожим, однако я не знал, есть ли у Flask какое-то волшебство, чтобы упростить задачу, как предлагалось в сообщении списка рассылки.

Изменить: для пояснения так выглядит моя модель

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

person mal-wan    schedule 18.08.2011    source источник


Ответы (15)


Похоже, вы на самом деле не выполнили свой запрос. Попробуйте следующее:

return jsonify(json_list = qryresult.all())

[Edit]: проблема с jsonify заключается в том, что обычно объекты не могут быть jsonified автоматически. Даже Python datetime не работает;)

В прошлом я добавил дополнительное свойство (например, serialize) к классам, которые необходимо сериализовать.

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]

А теперь для просмотров я могу просто сделать:

return jsonify(json_list=[i.serialize for i in qryresult.all()])

Надеюсь это поможет ;)

[Edit 2019]: если у вас есть более сложные объекты или циклические ссылки, используйте такую ​​библиотеку, как marshmallow < / а>).

person plaes    schedule 18.08.2011
comment
Хм, это изменило ошибку, теперь я получаю сообщение об ошибке со ссылкой на объект SQLAlchemy, например: myapp.models.Rating object at 0x102f25c10&gt; is not JSON serializable. Есть подсказка? Объект содержит только строки и интенты. - person mal-wan; 18.08.2011
comment
Похоже, ваша Рейтинговая модель содержит отношения с другими объектами, поэтому подождите секунду, я обновлю свой ответ .. - person plaes; 18.08.2011
comment
Но это не так, я добавил определение модели к моему исходному вопросу. Я ценю вашу помощь! - person mal-wan; 18.08.2011
comment
Спасибо! Вот и все !! Для всех, кто читает это, вызов должен быть return jsonify(json_list=[i.serialize for i in qryresult.all()]) для примера, приведенного выше (т.е. не dump). - person mal-wan; 19.08.2011
comment
@ mwan100, спасибо. В моем исходном коде у меня было свойство dump, я не знаю, почему я хотел это изменить: S - person plaes; 19.08.2011
comment
Интересно узнать, почему вы решили сделать serialize свойством, а не функцией? - person Mohamed; 31.08.2018
comment
@Mohamed 7 лет назад это имело смысл. Я создал serialize это свойство, потому что мне не нужно было выдвигать к нему какие-либо аргументы .. Конечно, as_json было бы лучше. - person plaes; 31.08.2018
comment
@plaes Как бы вы поступили с двумя моделями со многими отношениями, которые ссылаются друг на друга? Например, модель User имеет comments, а модель Comment имеет прикрепленный к ней User. Если вы вызовете сериализацию для любого из них, вы получите ошибку рекурсии. - person Darius Mandres; 23.07.2019
comment
Я действительно не занимаюсь этим. И этот ответ был быстрым решением ... - person plaes; 27.07.2019

Вот чего мне обычно достаточно:

Я создаю миксин сериализации, который использую со своими моделями. Функция сериализации в основном выбирает все атрибуты, которые предоставляет инспектор SQLAlchemy, и помещает их в dict.

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]

Все, что сейчас нужно, - это расширить модель SQLAlchemy с помощью класса миксина Serializer.

Если есть поля, которые вы не хотите раскрывать или которые требуют специального форматирования, просто переопределите функцию serialize() в подклассе модели.

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), если запрос приводит к списку) для результатов:

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))
person Carl Ekerot    schedule 14.01.2015
comment
Если вы используете jsonify для get_users, ваш синтаксис должен быть следующим: return jsonify (users = User.serialize_list (users)) - person idbill; 12.05.2015
comment
Я думаю, что этот ответ намного лучше, чем другие, спасибо. inspect() убийца. - person robru; 30.09.2015
comment
У меня это работает. - person Anil Jagtap; 27.04.2020
comment
Быстро и легко, спасибо большое! - person Atem18; 01.07.2020
comment
Это хорошее решение. Но это не сработает для вложенных атрибутов, требующих сериализации. - person Faizi; 30.03.2021
comment
Это здорово, спасибо !! - person antonmills; 18.06.2021
comment
Мне пришлось настроить метод сериализации, чтобы пропустить мои внешние ключи, иначе он кричит: «myForeignObject не сериализуем». (те атрибуты, которые вы установили с помощью db.relationship (...)) - person Alex; 12.07.2021

У меня была такая же потребность в сериализации в json. Взгляните на этот вопрос. Он показывает, как программно обнаруживать столбцы. Итак, из этого я создал код ниже. У меня это работает, и я буду использовать его в своем веб-приложении. Удачного кодирования!


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__)
person bitcycle    schedule 17.03.2012
comment
Похоже, это хорошо подходит для моего текущего проекта, но я использую недекларативные модели. Таким образом, не похоже, что у меня есть доступ к __table__ в классе даже после выполнения сопоставления. Есть мысли о том, как адаптировать to_json для недекларативной модели? - person technomalogical; 18.05.2012
comment
В итоге я просто добавил свои собственные Table объекты к каждой модели (__table__ = my_table_instance), которые, похоже, сработали. - person technomalogical; 18.05.2012
comment
Также вы можете расширить декларативный базовый класс, чтобы автоматически включать свойство json во все ваши модели. - person edsioufi; 02.09.2013
comment
Как мне заставить это работать с datetime? Я закончил тем, что добавил sqlalchemy.sql.sqltypes.Date к convert dict, а затем изменил каждый экземпляр c.type на type(c.type). - person Matthew Moisen; 30.05.2016
comment
@bitcycle В каких случаях имя столбца будет None? - person Layla; 13.07.2017
comment
Вот аналогичное решение: wakatime.com/blog/32-part -1-sqlalchemy-models-to-json - person Alan Hamlett; 12.07.2018

Вот мой подход: https://github.com/n0nSmoker/SQLAlchemy-serializer

pip установить SQLAlchemy-serializer

Вы можете легко добавить миксин в свою модель, а не просто вызвать метод .to_dict () на его экземпляре

Вы также можете написать свой собственный миксин на основе SerializerMixin.

person n0nSmoker    schedule 14.01.2013
comment
Интересное решение. Мне пришлось добавить elif isinstance (value, str): ret = value перед elif hasattr (value, 'iter'): в python3, чтобы избежать бесконечной рекурсии - person Shaun; 07.06.2014
comment
Я также вынул значение if: проверьте get_public, потому что оно отбрасывало целые числа = 0 - person Shaun; 07.06.2014

Хорошо, я работал над этим несколько часов и разработал то, что считаю самым питоническим решением. Следующие ниже фрагменты кода представляют собой python3, но при необходимости их не должно быть слишком болезненно для резервного копирования.

Первое, что мы собираемся сделать, это начать с миксина, который заставляет ваши модели БД действовать как dicts:

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

Теперь мы собираемся определить нашу модель, унаследовав миксин:

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, чтобы получить оставшуюся часть пути:

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 содержали поля, которые на самом деле не хранятся в базе данных), это тоже легко. Просто определите свои вычисляемые поля как @propertys и расширьте метод keys() следующим образом:

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 тривиально:

@app.route('/whatever', methods=['GET'])
def whatever():
    return jsonify(dict(results=MyModel.query.all()))
person robru    schedule 30.09.2015
comment
Думаю, ваш ответ чем-то похож на то, что сделал я. - person ; 02.06.2016
comment
Хороший ответ, потому что он работает с оригинальным flask.jsonify () - person Michael Ribbons; 11.12.2018

Для плоского запроса (без объединений) вы можете сделать это

@app.route('/results/')
def results():
    data = Table.query.all()
    result = [d.__dict__ for d in data]
    return jsonify(result=result)

и если вы хотите вернуть только определенные столбцы из базы данных, вы можете сделать это

@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)
person reubano    schedule 03.05.2013

Если вы используете flask-restful, вы можете использовать маршал:

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 и идентификатора.

person Adversus    schedule 26.12.2015

Вот мой ответ, если вы используете декларативную базу (с помощью некоторых из уже опубликованных ответов):

# 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)
person VinnyQ77    schedule 29.08.2016

Вот способ добавить метод as_dict () для каждого класса, а также любой другой метод, который вы хотите иметь для каждого отдельного класса. Не уверен, что это желаемый способ или нет, но он работает ...

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)
person tahoe    schedule 05.05.2015

Я занимался этой проблемой большую часть дня, и вот что я придумал (кредит на https://stackoverflow.com/a/5249214/196358 за то, что указали мне в этом направлении).

(Примечание: я использую flask-sqlalchemy, поэтому мой формат объявления модели немного отличается от обычного sqlalchemy).

В моем models.py файле:

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

и все мои объекты модели выглядят так:

class User(db.Model, Serializer):
  __public__ = ['id','username']
  ... field definitions ...

В своих представлениях я вызываю SWJsonify везде, где я бы назвал Jsonify, вот так:

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

Кажется, работает очень хорошо. Даже по отношениям. Я не продвинулся далеко с этим, так что YMMV, но пока это кажется мне довольно "правильным".

Предложения приветствуются.

person Kenny Winker    schedule 06.07.2012

Flask-Restful 0.3.6 Анализатор запросов рекомендует зефир

marshmallow - это ORM / ODM / независимая от фреймворка библиотека для преобразования сложных типов данных, таких как объекты, в собственные типы данных Python и обратно.

Ниже показан простой пример зефира.

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

Основные функции содержат

Объявление схем
сериализация объектов («дамп»)
десериализация объектов («загрузка»)
обработка коллекций объектов
проверка
указание имен атрибутов
указание ключей сериализации / десериализации
Рефакторинг: неявное создание поля
Упорядочивание вывода
Поля «только для чтения» и «только для записи»
Указание значений сериализации / десериализации по умолчанию
Схемы вложения
Настраиваемые поля

person Shihe Zhang    schedule 22.10.2018

Я искал что-то вроде подхода rails, используемого в ActiveRecord to_json, и реализовал нечто подобное с помощью этого Mixin, будучи неудовлетворенным другими предложениями. Он обрабатывает вложенные модели, а также включает или исключает атрибуты верхнего уровня или вложенных моделей.

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

class SerializableBaseQuery(BaseQuery):

    def serialize(self, include={}, exclude=[], only=[]):
        return [m.serialize(include, exclude, only) for m in self]

Для следующих моделей

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

Вы могли бы сделать что-то вроде

@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)
person zwalker    schedule 17.07.2016

Я работал с sql-запросом defaultdict списков объектов RowProxy с именем jobDict. Мне потребовалось некоторое время, чтобы выяснить, к какому типу относятся эти объекты.

Это был действительно простой и быстрый способ решить некоторую чистую jsonEncoding, просто привязывая строку к списку и первоначально определяя dict со значением list.

    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)
person Nick    schedule 09.05.2014

Я просто хочу добавить свой метод для этого.

просто определите пользовательский кодировщик json для сериализации ваших моделей db.

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

тогда в вашей функции просмотра

parents = Parent.query.all()
dat = json.dumps({"data": parents}, cls=ParentEncoder)
resp = Response(response=dat, status=200, mimetype="application/json")
return (resp)

это работает хорошо, хотя у родителей есть отношения

person Community    schedule 02.06.2016

Это было много раз и есть много правильных ответов, но следующий блок кода, похоже, работает:

my_object = SqlAlchemyModel()
my_serializable_obj = my_object.__dict__
del my_serializable_obj["_sa_instance_state"]
print(jsonify(my_serializable_object))

Я знаю, что это не идеальное решение и не такое элегантное, как другие, однако для тех, кто хочет быстрого исправления, они могут попробовать это.

person radalin    schedule 22.10.2014