Як серіалізувати результат SqlAlchemy до JSON?


192

У Django є хороша автоматична серіалізація моделей ORM, повернута з БД у формат JSON.

Як серіалізувати результат запиту SQLAlchemy до формату JSON?

Я спробував, jsonpickle.encodeале він кодує сам об’єкт запиту. Я спробував, json.dumps(items)але повертається

TypeError: <Product('3', 'some name', 'some desc')> is not JSON serializable

Невже так важко серіалізувати об'єкти SQLAlchemy ORM в JSON / XML? Чи немає для нього серіалізатора за замовчуванням? Сьогодні дуже поширена задача серіалізувати результати запитів ORM.

Мені потрібно лише повернути JSON або XML-представлення даних результату запитів SQLAlchemy.

Результат запитів об’єктів SQLAlchemy у форматі JSON / XML необхідний для використання у форматі даних JavaScript (JQGrid http://www.trirand.com/blog/ )


Це рішення, яке працює для мене. введіть опис посилання тут
октаедро

Відповіді:


129

Плоска реалізація

Ви можете використовувати щось подібне:

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 за допомогою:

c = YourAlchemyClass()
print json.dumps(c, cls=AlchemyEncoder)

Він буде ігнорувати поля, які не кодуються (встановіть їх у "None").

Він не розширює відносини автоматично (оскільки це може призвести до самонавіювання та вільного циклу).

Рекурсивна некругова реалізація

Якщо, однак, ви віддаєте перевагу циклу назавжди, ви можете використовувати:

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

А потім кодуйте об'єкти, використовуючи:

print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False)

Це кодувало б усіх дітей, і всіх їх дітей, і всіх їх дітей ... По суті, можливо, кодуйте всю вашу базу даних. Коли вона досягне щось, що було закодовано раніше, воно кодуватиме це як "Ніхто".

Рекурсивна, можливо, кругова, вибіркова реалізація

Ще одна альтернатива, напевно, краща - це можливість вказати поля, які потрібно розширити:

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

Тепер ви можете зателефонувати за допомогою:

print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False)

Наприклад, лише розширити поля SQLAlchemy під назвою "батьки".


це чудова відповідь, однак я отримую "не міг кодувати" BaseQuery "кожного разу, коли він потрапляє у взаємозв'язок з неплоскими методами, будь-якими ідеями?
Бен Кіла

1
@SashaB Як щодо орієнтації більш детально на випадки, коли відносини повторюються? Наприклад, якщо я маю online_orderі addressобидва мають відношення до user, але online_orderтакож має відношення до address. Якби я хотів серіалізувати все це, я повинен був би включити його addressдо цього fields_to_expand, але я не хотів би його надмірно серіалізувати addressчерез його стосунки до обох userі online_order.
Кріпсі

2
@BenKilah Дозвольте здогадатися, ви використовуєте Flask-SqlAlchemy, і ваші моделі успадковуються від db.Model, а не від Base. Якщо це так, змініть for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:так, щоб він читав for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:. Майте на увазі, що це рішення не дозволить вам мати власність / відносини з назвою "запит"
Pakman

так само, як я, але набагато складніший. stackoverflow.com/questions/7102754/…
tyan

2
Ви можете використовувати моє рішення github.com/n0nSmoker/SQLAlchemy-serializer
n0nSmoker

272

Ви можете просто вивести об'єкт у вигляді словника:

class User:
   def as_dict(self):
       return {c.name: getattr(self, c.name) for c in self.__table__.columns}

А потім ви використовуєте User.as_dict()для серіалізації вашого об'єкта.

Як пояснено в Перетворити об'єкт рядка sqlalchemy в dict python


2
@charlax, як я можу виправити DateTime? За допомогою цього я отримую 'datetime.datetime (2013, 3, 22, 16, 50, 11) не JSON сериализации' , коли я json.dumps
Asken

1
Це відповідальність за JSONEncoderоб'єкт. Ви можете підкласифікувати його, щоб визначити власний кодер для якогось об'єкта, включаючи дату. Зверніть увагу, що Flask, наприклад, підтримується кодування дати часу в JSON поза коробкою (з останньою версією).
шарлакс

3
Якщо ви використовуєте "декларативний" метод sqlalchemy, ви можете додати щось подібне до користувальницького базового класу - це дуже зручно, оскільки ви можете викликати my_orm_object.toDict () на будь-який об'єкт ORM. Аналогічно ви можете визначити метод .toJSON (), який використовує ваш метод toDict та спеціальний кодер для обробки дат, крапок тощо
FredL

7
також підтримувати дату:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
Шохам

1
Це не працює, якщо змінні вашого класу не збігаються з назвами стовпців. Будь-яка ідея, як отримати назви класів замість цього?
Джеймс Берк

55

Ви можете перетворити RowProxy в диктат, як це:

 d = dict(row.items())

Потім серіалізуйте це в JSON (вам потрібно буде вказати кодер для таких речей, як datetimeзначення) Це не так складно, якщо ви хочете лише один запис (а не повну ієрархію пов'язаних записів).

json.dumps([(dict(row.items())) for row in rs])

1
Це працює для мого користувальницького запиту sql з db.engine.connect () як con: rs = con.execute (sql)
JZ.

1
Це набагато простіше і працює. Чим відрізняється ця відповідь від прийнятої відповіді?
Сонце

46

Рекомендую використовувати зефір . Це дозволяє створити серіалізатори для представлення екземплярів вашої моделі з підтримкою відносин та вкладених об'єктів.

Ось усічений приклад з їхніх документів. Візьміть модель ORM Author:

class Author(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first = db.Column(db.String(80))
    last = db.Column(db.String(80))

Схема зефіру для цього класу будується так:

class AuthorSchema(Schema):
    id = fields.Int(dump_only=True)
    first = fields.Str()
    last = fields.Str()
    formatted_name = fields.Method("format_name", dump_only=True)

    def format_name(self, author):
        return "{}, {}".format(author.last, author.first)

... і використовується так:

author_schema = AuthorSchema()
author_schema.dump(Author.query.first())

... дало б такий вихід:

{
        "first": "Tim",
        "formatted_name": "Peters, Tim",
        "id": 1,
        "last": "Peters"
}

Погляньте на їх повний приклад Flask-SQLAlchemy .

Бібліотека, що називається, marshmallow-sqlalchemyспеціально інтегрує SQLAlchemy та зефір. У цій бібліотеці схема для Authorописаної вище моделі виглядає приблизно так:

class AuthorSchema(ModelSchema):
    class Meta:
        model = Author

Інтеграція дозволяє виводити типи полів із типів SQLAlchemy Column.

зефір-пллалхімія тут.


12
Я також знайшов marshmallow-sqlalchemy.readthedocs.io/en/latest, який спрощує генерацію схеми
Foo L

44

Python 3.7+ та Flask 1.1+ можуть використовувати вбудований пакет даних класів

from dataclasses import dataclass
from datetime import datetime
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
db = SQLAlchemy(app)


@dataclass
class User(db.Model):
  id: int
  email: str

  id = db.Column(db.Integer, primary_key=True, auto_increment=True)
  email = db.Column(db.String(200), unique=True)


@app.route('/users/')
def users():
  users = User.query.all()
  return jsonify(users)  


if __name__ == "__main__":
  users = User(email="user1@gmail.com"), User(email="user2@gmail.com")
  db.create_all()
  db.session.add_all(users)
  db.session.commit()
  app.run()

Тепер /users/маршрут поверне список користувачів.

[
  {"email": "user1@gmail.com", "id": 1},
  {"email": "user2@gmail.com", "id": 2}
]

Автосеріалізувати пов'язані моделі

@dataclass
class Account(db.Model):
  id: int
  users: User

  id = db.Column(db.Integer)
  users = db.relationship(User)  # User model would need a db.ForeignKey field

Відповідь jsonify(account)була б така.

{  
   "id":1,
   "users":[  
      {  
         "email":"user1@gmail.com",
         "id":1
      },
      {  
         "email":"user2@gmail.com",
         "id":2
      }
   ]
}

Перезапишіть кодер JSON за замовчуванням

from flask.json import JSONEncoder


class CustomJSONEncoder(JSONEncoder):
  "Add support for serializing timedeltas"

  def default(o):
    if type(o) == datetime.timedelta:
      return str(o)
    elif type(o) == datetime.datetime:
      return o.isoformat()
    else:
      return super().default(o)

app.json_encoder = CustomJSONEncoder      

1
Це виглядає як правильний вид простого. Це також працює для десеріалізації?
Ender2050

Ви можете перетворити словник аналізованої JSON в модель , використовуючи ключове слово аргумент розпакування:data = request.json['user']; user = User(**data)
Том

3
Зауважте, що id: int = Columnце спрацює, але id = Columnне буде, схоже, ВИ ВДАЛИ оголосити статичне введення для json серіалізованого поля, інакше ви отримаєте порожній {}об’єкт.
Амбруаз Rabier

1
Це працювало для мене, чому це не прийнята відповідь? Я годинами граю навколо app_context, щоб змусити це працювати з Flask-Marshmallow.
Нік Дат Ле

1
Працював і для мене. Зверніть увагу , що якщо ви на Python 3.6, ви хочете просто встановити пакет: pipenv install dataclasses. І тоді це буде добре.
ОлександрХ

14

Пакет Flask-JsonTools має реалізацію класу JsonSerializableBase Base для ваших моделей.

Використання:

from sqlalchemy.ext.declarative import declarative_base
from flask.ext.jsontools import JsonSerializableBase

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Тепер Userмодель магічно серіалізується.

Якщо ваша рамка не є колбою, ви можете просто захопити код


2
Це вирішує лише половину проблеми, оскільки це лише серіалізує один ряд. Як серіалізувати весь результат запиту?
Стів Беннетт

@SteveBennett використовуйте jsontopi ' jsonapi для кодування відповіді. Це автоматично кодує об’єкт, що повертається
Tjorriemorrie

У мене дуже проста модель sqlalchemy, і я отримую: TypeError: <ORM.State object at 0x03577A50> не є JSON серіалізаційним
Matej

1
Врешті-решт це спрацювало, явно викликавши __json __ () на моєму об'єкті моделі: return my_object .__ json __ ()
Matej

Бібліотека не працює з Flask 1.0 і вище, як import flask.ext.whateverце більше не підтримується у Flask 1.0.
Adarsh ​​Madrecha

14

З міркувань безпеки ви ніколи не повинні повертати всі поля моделі. Я віддаю перевагу вибірково вибирати їх.

Кодування json Flask тепер підтримує UUID, дату та зв'язки (додається queryі query_classдля db.Modelкласу flask_sqlalchemy ). Я оновив кодер так:

app / json_encoder.py

    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

# json encoding
from app.json_encoder import AlchemyEncoder
app.json_encoder = AlchemyEncoder

З цим я можу додатково додати __json__властивість, яка повертає список полів, які я хочу кодувати:

app/models.py

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 до моєї точки зору, повертаю список результатів, і тоді мій результат виглядає наступним чином:

[

{

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

]

Гарний! Ще раз, доказ того, що іноді вам не потрібен жирний пакет для кожного нерозумного маленького завдання - що вивчити DSL може бути складніше, ніж робити це "важким" способом. Я переглянув багато пакетів JSON та REST перед посадкою тут. Правда, це по- , як і раніше вимагає пакет, flask_jsontools (додати @jsonapiдо @app.routeв views.py і т.д.), але я люблю простоту цього. Я думаю, що це дешевий Flask додав дату, але не дату, тому я сам додав її до json_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
juanitogan

10

Ви можете використовувати самоаналіз SqlAlchemy так:

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 в dict python


5

Більш детальне пояснення. У своїй моделі додайте:

def as_dict(self):
       return {c.name: str(getattr(self, c.name)) for c in self.__table__.columns}

Це str()для python 3, тож якщо використовується python 2, використовуйтеunicode() . Це повинно допомогти десяріалізувати дати. Ви можете видалити його, якщо не маєте справу з ними.

Тепер ви можете запитувати подібну базу даних

some_result = User.query.filter_by(id=current_user.id).first().as_dict()

First()потрібен, щоб уникнути дивних помилок. as_dict()тепер десеріалізуватиме результат. Після десяріалізації він готовий перейти до json

jsonify(some_result)

3

Це не так прямо. Я написав якийсь код для цього. Я все ще працюю над цим, і він використовує структуру MochiKit. В основному це перекладає складні об'єкти між Python та Javascript за допомогою проксі та зареєстрованих JSON-перетворювачів.

Сторона браузера для об'єктів бази даних - це db.js Для цього потрібне основне джерело проксі- файлів Python в proxy.js .

На стороні Python розташований базовий проксі-модуль . Потім нарешті кодер об'єкта SqlAlchemy в webserver.py . Це також залежить від екстракторів метаданих, знайдених у файлі models.py .


Досить складне з першого погляду ... Що мені потрібно - це отримати результат запитів об’єктів SQLAlchemy у форматі JSON / XML, щоб використовувати його у javascript datagird (JQGrid trirand.com/blog )
Zelid

Іноді проблеми складніші, ніж ви виконуєте на перший погляд ... Це обробляє об'єкти, повернуті як сторонні ключі, і намагається уникнути нескінченної рекурсії, що відбувається з глибоко вкладеними відносинами. Однак ви, ймовірно, можете написати деякі власні запити, які повертають лише базові типи та серіалізують ті, хто має simplejson безпосередньо.
Кіт

1
Правильно, можливо, я дійсно перейду із запитами на дікти, використовуючи SQLAlchemy, і я буду використовувати переваги ORM, виконуючи лише функції збереження / оновлення.
Зелід

3

Хоча оригінальне запитання дещо відступає, кількість відповідей тут (і мій власний досвід) дозволяє припустити, що це нетривіальне питання з безліччю різних підходів різної складності при різних компромісах.

Ось чому я побудував SQLAthanor бібліотеку яка розширює декларативний ORM SQLAlchemy з налаштованою підтримкою серіалізації / де-серіалізації, на яку, можливо, ви захочете поглянути.

Бібліотека підтримує:

  • Python 2.7, 3.4, 3.5 та 3.6.
  • Версії SQLAlchemy 0.9 і новіші
  • серіалізація / де-серіалізація до / від JSON, CSV, YAML та Python dict
  • серіалізація / де-серіалізація стовпців / атрибутів, зв’язків, гібридних властивостей та проксі-серверів асоціації
  • включення та відключення серіалізації для певних форматів та стовпців / зв’язків / атрибутів (наприклад, ви хочете підтримувати вхідне password значення, але ніколи не включати вихідне )
  • досериалізація та обробка значень після десеріалізації (для перевірки чи примусу типу)
  • досить простий синтаксис, який одночасно є і пітонічним, і цілком відповідає власному підходу SQLAlchemy

Ви можете ознайомитись із (сподіваюсь!) Вичерпними документами тут: https://sqlathanor.readthedocs.io/en/latest

Сподіваюся, це допомагає!


2

Спеціальна серіалізація та десеріалізація.

"from_json" (метод класу) будує об'єкт Model на основі даних json.

"десеріалізацію" можна викликати лише в екземплярі, і об'єднати всі дані з json в модельний екземпляр.

"серіалізувати" - рекурсивна серіалізація

Властивість __write_only__ потрібна для визначення властивостей лише для запису (наприклад, "password_hash").

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

2

Ось рішення, яке дозволяє вибирати відносини, які ви хочете включити у свій результат, настільки глибокі, як ви хотіли б піти. ПРИМІТКА. Це повне переписування, приймаючи dict / str як аргумент, а не список. виправляє деякі речі ..

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, все що вам потрібно

json.dumps(person.deep_dict(relations={'family':{'homes':'rooms'}}))

Це добре, я думаю, що просто поставити у ваш базовий клас, щоб усі об’єкти його мали. Я залишу кодування json вам ...
tahoe

Зауважте, що ця версія отримає всі відносини у списку, тому будьте обережні, надаючи стосунки з тоннами предметів ...
tahoe

1
def alc2json(row):
    return dict([(col, str(getattr(row,col))) for col in row.__table__.columns.keys()])

Я думав, що я пограю в цей гольф з невеликим кодом.

FYI: Я використовую automap_base, оскільки у нас є окремо розроблена схема відповідно до бізнес-вимог. Я щойно почав використовувати SQLAlchemy сьогодні, але в документації зазначено, що automap_base - це розширення до заяви_бази, яке, здається, є типовою парадигмою в ORQ SQLAlchemy, тому я вважаю, що це повинно працювати.

Він не захоплюється наступними зовнішніми ключами за рішенням Tjorriemorrie , але він просто відповідає стовпцям значенням та обробляє типи Python на str () із значеннями стовпців. Наші значення складаються з Python datetime.time та decimal.Decimal результатів типу класу, щоб це було виконано.

Сподіваюся, це допоможе будь-яким перехожим!


1

Я знаю, що це досить старий пост. Я прийняв рішення, подане @SashaB, і змінив відповідно до моєї потреби.

До нього я додав наступні речі:

  1. Список ігнорування полів: список полів, які слід ігнорувати під час серіалізації
  2. Список заміни поля: Словник, що містить імена полів, які слід замінити значеннями під час серіалізації.
  3. Видалені методи та серіалізація BaseQuery

Мій код такий:

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

Сподіваюся, це комусь допоможе!


1

Використовуйте вбудований серіалізатор у SQLAlchemy:

from sqlalchemy.ext.serializer import loads, dumps
obj = MyAlchemyObject()
# serialize object
serialized_obj = dumps(obj)

# deserialize object
obj = loads(serialized_obj)

Якщо ви переносите об'єкт між сеансами, не забудьте від'єднати об'єкт від поточного сеансу за допомогою session.expunge(obj). Щоб прикріпити його знову, просто зробіть session.add(obj).


Nifty, але не перетворюється на JSON.
blakev

2
Для "серіалізації" JSON ознайомтеся з зефіром-квалахімією . Безумовно, найкраще рішення, коли ви піддаєте об’єкти клієнтам. marshmallow-sqlalchemy.readthedocs.io
chribsen

Модуль серіалізатора підходить тільки для структур запитів. Він не потрібен для: екземплярів визначених користувачем класів. Вони не містять посилань на двигуни, сеанси чи конструкції виразів у типовому випадку і можуть бути серіалізовані безпосередньо.
тис.

1

наступний код буде серіалізувати результат sqlalchemy до json.

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) 

Закликаючи весело,

def all_products():
    all_products = Products.query.all()
    return to_array(all_products)

1

AlchemyEncoder чудовий, але іноді не відповідає десятковим значенням. Ось покращений кодер, який вирішує десяткову задачу -

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)

1

При використанні sqlalchemy для підключення до db I це просте рішення, яке легко налаштовується. Використовуйте панди.

import pandas as pd
import sqlalchemy

#sqlalchemy engine configuration
engine = sqlalchemy.create_engine....

def my_function():
  #read in from sql directly into a pandas dataframe
  #check the pandas documentation for additional config options
  sql_DF = pd.read_sql_table("table_name", con=engine)

  # "orient" is optional here but allows you to specify the json formatting you require
  sql_json = sql_DF.to_json(orient="index")

  return sql_json

1
step1:
class CNAME:
   ...
   def as_dict(self):
       return {item.name: getattr(self, item.name) for item in self.__table__.columns}

step2:
list = []
for data in session.query(CNAME).all():
    list.append(data.as_dict())

step3:
return jsonify(list)

3
Скидання коду без будь-якого пояснення дуже рідко корисні. Переповнення стека - це вивчення, а не надання фрагментів для сліпого копіювання та вставки. Будь ласка, відредагуйте своє запитання та поясніть, як воно працює краще, ніж те, що надала ОП.
Кріс

0

Під Flask це працює і обробляє поля даних timetime, перетворюючи поле типу
'time': datetime.datetime(2018, 3, 22, 15, 40)у
"time": "2018-03-22 15:40:00":

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)

0

Вбудований дросель серіалізатора з utf-8 не може декодувати недійсний початковий байт для деяких входів. Натомість я пішов із:

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

0

Можливо, ви можете використовувати такий клас

from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy import Table


class Custom:
    """Some custom logic here!"""

    __table__: Table  # def for mypy

    @declared_attr
    def __tablename__(cls):  # pylint: disable=no-self-argument
        return cls.__name__  # pylint: disable= no-member

    def to_dict(self) -> Dict[str, Any]:
        """Serializes only column data."""
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}

Base = declarative_base(cls=Custom)

class MyOwnTable(Base):
    #COLUMNS!

При цьому всі об'єкти мають to_dictметод


0

Під час використання декількох необроблених sql та невизначених об'єктів, використовуючи, як cursor.descriptionвидається, можна отримати те, що я шукав:

with connection.cursor() as cur:
    print(query)
    cur.execute(query)
    for item in cur.fetchall():
        row = {column.name: item[i] for i, column in enumerate(cur.description)}
        print(row)

-2

Мій словник з використанням (занадто багато?) Словників:

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

Запуск з колбою (включаючи jsonify) та flask_sqlalchemy для друку виходів як JSON.

Викличте функцію за допомогою jsonify (serialize ()).

Працює з усіма запитами SQLAlchemy, які я намагався до цього часу (працює з SQLite3)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.