Как сериализовать SqlAlchemy результат в JSON? - PullRequest
146 голосов
/ 17 февраля 2011

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 datagird (JQGrid http://www.trirand.com/blog/)

Ответы [ 20 ]

222 голосов
/ 09 августа 2012

Вы можете просто вывести свой объект в виде dict:

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

111 голосов
/ 19 мая 2012

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

Вы можете использовать что-то вроде этого:

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)

Он будет игнорировать поля, которые не кодируются (установите для них значение «Нет»).

Он не расширяет отношения автоматически (поскольку это может привести к самоссылке и циклу навсегда).

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

Если, однако, вы бы предпочли цикл навсегда, вы можете использовать:

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, например, "родители".

47 голосов
/ 12 августа 2011

Вы можете преобразовать RowProxy в dict следующим образом:

 d = dict(row.items())

Затем сериализовать это в JSON (вам нужно будет указать кодировщик для таких вещей, как datetime значения) Это не так сложно, если выпросто хочу одну запись (а не полную иерархию связанных записей).

json.dumps([(dict(row.items())) for row in rs])
38 голосов
/ 12 мая 2014

Я рекомендую использовать недавно появившуюся библиотеку marshmallow .Он позволяет вам создавать сериализаторы для представления экземпляров вашей модели с поддержкой отношений и вложенных объектов.

Посмотрите на их Пример SQLAlchemy .

14 голосов
/ 06 сентября 2014

Flask-JsonTools имеет реализацию JsonSerializableBase Базовый класс для ваших моделей.

Использование:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Теперь *Модель 1010 * магически сериализуема.

Если ваш фреймворк не Flask, вы можете просто получить код

12 голосов
/ 22 июля 2015

По соображениям безопасности вы никогда не должны возвращать все поля модели. Я предпочитаю выборочно выбирать их.

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

Приложение / 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"
}

]
10 голосов
/ 12 сентября 2017

Вы можете использовать самоанализ 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 в python dict

4 голосов
/ 11 мая 2018

Более подробное объяснение.В вашей модели добавьте:

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 голосов
/ 17 февраля 2011

Это не так просто. Я написал код для этого. Я все еще работаю над этим, и он использует фреймворк MochiKit. Он в основном транслирует составные объекты между Python и Javascript, используя прокси и зарегистрированные конверторы JSON.

Сторона браузера для объектов базы данных: db.js Требуется базовый источник прокси Python в proxy.js .

На стороне Python есть базовый прокси-модуль . Затем, наконец, объектный кодировщик SqlAlchemy в webserver.py . Это также зависит от экстракторов метаданных, найденных в файле models.py .

2 голосов
/ 18 декабря 2016

Вот решение, которое позволяет вам выбрать отношения, которые вы хотите включить в свой вывод, настолько глубоко, насколько вы хотели бы. ПРИМЕЧАНИЕ. Это полная перезапись, в которой в качестве аргумента используется аргумент 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'}}))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...