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



У Django есть хорошая автоматическая сериализация моделей ORM, возвращенных из DB в формат 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/)

1110   18  

18 ответов:

плоский реализации

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

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

вы можете просто вывести свой объект как 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

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

 d = dict(row.items())

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

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

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

взгляните на них Пример SQLAlchemy.

Колба-JsonTools пакет имеет реализацию JsonSerializableBase базовый класс для моделей.

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

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

теперь User модель магически сериализуема.

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

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

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

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 в мое представление, возвращаю resultlist, а затем мой вывод выглядит следующим образом:

[

{

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

]

вы можете использовать интроспекцию 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

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

стороне браузера для объектов базы данных db.js Ему нужен основной источник прокси Python в прокси.js.

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

пользовательская сериализация и десериализация.

"from_json" (метод класса) строит объект модели на основе данных json.

"десериализации" может быть вызван только на экземпляре, и объединить все данные из json в экземпляр модели.

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

__писать_только__ свойство необходимо для определения свойств только для записи ("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

вот это решение, которое позволяет выбрать отношения, которые вы хотите включить в свой выходной так глубоко, как вы хотели бы пойти. Примечание: это полная переписать принимая dict/str как arg, а не список. исправляет некоторые вещи..

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

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

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

библиотека поддержка:

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

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

надеюсь, что это помогает!

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

Я думал, что сыграю немного в кодовый гольф с этим.

FYI: я использую automap_base так как у нас есть отдельно разработанная схема в соответствии с бизнес-требованиями. Я только начал использовать SQLAlchemy сегодня, но в документации говорится, что automap_base-это расширение declarative_base, которое, похоже, является типичной парадигмой в SQLAlchemy ORM, поэтому я считаю, что это должно работать.

не заморачиваться с внешними ключами в Tjorriemorrieрешение, но оно просто сопоставляет столбцы со значениями и обрабатывает типы Python с помощью str()-ing значения столбцов. Наши ценности состоят из Python datetime.время и десятичное число.Десятичный тип класса приводит к тому, что он выполняет задание.

надеюсь, это поможет любым прохожим!

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

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

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

следующий код будет сериализовать результат 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)

Я знаю, это довольно старый пост. Я взял решение, данное @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

надеюсь, что это поможет кому-то!

использовать встроенный сериализатор в 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).

под колбой, это работает и обрабатывает поля datatime, Преобразуя поле типа
'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)

мое использование взятия (слишком много?) словари:

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

работает с flask (включая jsonify) и flask_sqlalchemy для печати выходных данных как JSON.

вызовите функцию с помощью jsonify (serialize ()).

работает со всеми запросами SQLAlchemy, которые я пробовал до сих пор (запуск SQLite3)

Comments

    Ничего не найдено.