Como serializar o resultado SqlAlchemy para JSON?


189

O Django possui uma boa serialização automática de modelos ORM retornados do DB para o formato JSON.

Como serializar o resultado da consulta SQLAlchemy para o formato JSON?

Eu tentei, jsonpickle.encodemas ele codifica o próprio objeto de consulta. Eu tentei json.dumps(items)mas volta

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

É realmente tão difícil serializar objetos SQLAlchemy ORM para JSON / XML? Não existe um serializador padrão para isso? Atualmente, é uma tarefa muito comum serializar resultados da consulta ORM.

O que eu preciso é apenas retornar a representação de dados JSON ou XML do resultado da consulta SQLAlchemy.

O resultado da consulta de objetos SQLAlchemy no formato JSON / XML é necessário para ser usado no datagird javascript (JQGrid http://www.trirand.com/blog/ )


Esta é uma solução alternativa que funciona para mim. digite a descrição do link aqui
— octaedro

Respostas:


127

Uma implementação plana

Você poderia usar algo como isto:

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)

e depois converta para JSON usando:

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

Ele ignorará os campos que não são codificáveis ​​(defina-os como 'Nenhum').

Ele não expande automaticamente as relações (pois isso pode levar a referências próprias e fazer um loop para sempre).

Uma implementação recursiva e não circular

Se, no entanto, você preferir fazer um loop para sempre, use:

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

E então codifique objetos usando:

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

Isso codifica todos os filhos, todos os filhos e todos os filhos ... Codifica potencialmente todo o banco de dados inteiro, basicamente. Quando alcança algo que foi codificado antes, o codificará como 'Nenhum'.

Uma implementação seletiva recursiva, possivelmente circular

Outra alternativa, provavelmente melhor, é poder especificar os campos que você deseja expandir:

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

Agora você pode chamá-lo com:

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

Para expandir apenas os campos SQLAlchemy chamados 'pais', por exemplo.


isso é uma grande resposta, no entanto eu recebo um "não foi possível codificar 'BaseQuery' sempre que ela atinge uma relação com os métodos não-planas, todas as idéias?
— Ben Kilah

1
@SashaB Que tal direcionar mais detalhadamente os casos em que um relacionamento é repetido? Por exemplo, se eu tiver online_ordere address, ambos com um relacionamento com user, mas online_ordertambém tiver um relacionamento com address. Se eu quisesse serializar tudo isso, teria que incluir addressno fields_to_expand, mas não gostaria de serializar redundantemente addressdevido ao seu relacionamento com ambos usere online_order.
— 28415 Chrispy

2
@BenKilah Deixe-me adivinhar, você está usando o Flask-SqlAlchemy e seus modelos são herdados do db.Model, não do Base. Se for esse o caso, modifique for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:para que ele leia for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:. Tenha em mente esta solução vai impedir que você tenha uma propriedade / relacionamento com 'consulta' o nome
— Pakman

da mesma maneira que eu fiz, mas muito mais complexo. stackoverflow.com/questions/7102754/…
— tyan

2
Você pode usar minha solução github.com/n0nSmoker/SQLAlchemy-serializer
— n0nSmoker

270

Você pode simplesmente enviar seu objeto como um dicionário:

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

E então você usa User.as_dict() para serializar seu objeto.

Conforme explicado em Converter objeto de linha sqlalchemy em python dict


2
@charlax, Como eu consertei um DateTime? Ao utilizar este I get 'datetime.datetime (2013, 3, 22, 16, 50, 11) não é JSON serializado' quando eu faço json.dumps
— Asken

1
É da responsabilidade do JSONEncoderobjeto. Você pode subclassificá-lo para definir seu próprio codificador para algum objeto, incluindo data e hora. Observe que Flask, por exemplo, suporta codificação datetime em JSON imediatamente (com a versão mais recente).
— charlax

3
Se você usar o método "declarativo" do sqlalchemy, poderá adicionar algo assim a uma classe Base personalizada - isso é bastante útil, pois você pode chamar my_orm_object.toDict () em qualquer objeto ORM que você tenha. Da mesma forma você pode definir um método .toJSON (), que usa o seu método toDict e um codificador personalizado para datas manipulação, gotas etc
— Fredl

7
para também suportar data e hora:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
— Shoham

1
Isso não funciona se suas variáveis ​​de classe não forem iguais aos nomes de suas colunas. Alguma idéia de como obter os nomes das turmas?
— James Burke

55

Você pode converter um RowProxy em um ditado como este:

 d = dict(row.items())

Em seguida, serialize isso para JSON (você precisará especificar um codificador para coisas como datetimevalores). Não é tão difícil se você deseja apenas um registro (e não uma hierarquia completa de registros relacionados).

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

1
Isso funciona para minha consulta sql personalizada com db.engine.connect () como con: rs = con.execute (sql)
— JZ.

1
Isso é muito mais simples e funciona. Qual é a diferença entre esta resposta e a resposta aceita?
— Sundeep 23/08/18

46

Eu recomendo usar marshmallow . Permite criar serializadores para representar suas instâncias de modelo com suporte a relações e objetos aninhados.

Aqui está um exemplo truncado de seus documentos. Veja o modelo 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))

Um esquema de marshmallow para essa classe é construído assim:

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)

... e usado assim:

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

... produziria uma saída como esta:

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

Veja o exemplo completo do Flask-SQLAlchemy .

Uma biblioteca chamada marshmallow-sqlalchemyintegra especificamente SQLAlchemy e marshmallow. Nessa biblioteca, o esquema para o Authormodelo descrito acima se parece com o seguinte:

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

A integração permite que os tipos de campo sejam inferidos a partir dos Columntipos SQLAlchemy .

marshmallow-sqlalchemy aqui.


12
I também encontrado marshmallow-sqlalchemy.readthedocs.io/en/latest o que simplifica o esquema geração
— Foo L

40

Python 3.7+ e Flask 1.1+ podem usar o pacote de classes de dados embutido

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

A /users/rota agora retornará uma lista de usuários.

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

Serializar automaticamente modelos relacionados

@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

A resposta jsonify(account)seria essa.

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

Substituir o codificador JSON padrão

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
Parece o tipo certo de simples. Também funciona para desserialização?
— precisa saber é o seguinte

Você pode converter um dicionário de JSON analisado em um modelo usando a descompactação de argumento de palavra-chave:data = request.json['user']; user = User(**data)
— tom

3
Observe que id: int = Columnfuncionará, mas id = Columnnão funcionará, parece que VOCÊ DEVE declarar a digitação estática para o json serializar o campo, caso contrário, você obterá um {}objeto vazio .
— Ambroise Rabier

1
Isso funcionou para mim, por que essa não é a resposta aceita? Estou brincando há app_context por horas para fazer isso funcionar com o Flask-Marshmallow.
— Nick Dat Le

1
Trabalhou para mim também. Note que se você estiver em Python 3.6, você quer apenas instalar o pacote: pipenv install dataclasses. E então vai funcionar muito bem.
— AleksandrH

14

O pacote Flask-JsonTools possui uma implementação da classe JsonSerializableBase Base para seus modelos.

Uso:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Agora o Usermodelo é magicamente serializável.

Se sua estrutura não é o Flask, você pode simplesmente pegar o código


2
Isso resolve apenas metade do problema, pois serializa apenas uma única linha. Como serializar todo o resultado da consulta?
— Steve Bennett

@SteveBennett usa o jsonapi dos jsontools para codificar a resposta. Que irá codificar o objeto de retorno
— Tjorriemorrie

Eu tenho um modelo sqlalchemy muito simples, e eu estou ficando: TypeError: <ORM.State objeto em 0x03577A50> não é JSON serializado
— Matej

1
Funcionou, eventualmente, chamando explicitamente __json __ () no meu objeto de modelo: retorno my_object .__ json __ ()
— Matej

A biblioteca não funciona com o Flask 1.0 e superior, pois import flask.ext.whatevernão é mais suportada no Flask 1.0.
— Adarsh ​​Madrecha 27/09/18

14

Por motivos de segurança, você nunca deve retornar todos os campos do modelo. Eu prefiro escolhê-los seletivamente.

A codificação json do Flask agora suporta UUID, data e hora (e foi adicionada querye query_classpara a db.Modelclasse flask_sqlalchemy ). Atualizei o codificador da seguinte maneira:

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

Com isso, opcionalmente, posso adicionar uma __json__propriedade que retorna a lista de campos que desejo codificar:

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

Eu adiciono @jsonapi à minha visualização, retorno a lista de resultados e, em seguida, minha saída é a seguinte:

[

{

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

]

Lindo! Mais uma vez, comprove que às vezes você não precisa de um pacote grande para cada pequena tarefa estúpida - que aprender DSL pode ser mais difícil do que fazê-lo da maneira "mais difícil". Eu olhei para muitos pacotes JSON e REST antes de chegar aqui. É verdade, isso ainda requer um pacote, flask_jsontools (para adicionar @jsonapia @app.routeem views.py etc), mas eu amo a simplicidade dele. Eu acho que é Flask barato acrescentou datetime, mas não data para que eu adicionei-me a json_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
— juanitogan

10

Você pode usar a introspecção de SqlAlchemy da seguinte maneira:

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

Inspire-se com uma resposta aqui: Converter objeto de linha sqlalchemy em dict python


5

Uma explicação mais detalhada. No seu modelo, adicione:

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

O str()é para python 3, portanto, se você estiver usando python 2, use unicode(). Deve ajudar a desserializar datas. Você pode removê-lo se não estiver lidando com eles.

Agora você pode consultar o banco de dados como este

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

First()é necessário para evitar erros estranhos. as_dict()agora desserializará o resultado. Após a desserialização, ele está pronto para ser transformado em json

jsonify(some_result)

3

Não é tão direto. Eu escrevi algum código para fazer isso. Ainda estou trabalhando nisso e ele usa a estrutura MochiKit. Ele basicamente traduz objetos compostos entre Python e Javascript usando um proxy e conversores JSON registrados.

O lado do navegador para objetos de banco de dados é o db.js Ele precisa da fonte básica de proxy Python no proxy.js .

No lado do Python, há o módulo proxy base . Finalmente, o codificador de objeto SqlAlchemy em webserver.py . Também depende dos extratores de metadados encontrados no arquivo models.py .


Muito complicado à primeira vista ... O que eu preciso - é obter SQLAlchemy objetos consulta resultado em JSON / formato XML para usá-lo em datagird javascript (JQGrid trirand.com/blog )
— Zelid

Às vezes, os problemas são mais complicados do que você imagina à primeira vista ... Isso lida com objetos retornados como chaves estrangeiras e tenta evitar a recursão infinita que ocorre com relações profundamente aninhadas. No entanto, você provavelmente pode escrever algumas consultas personalizadas que retornam apenas tipos de base e serializam diretamente aquelas com simplejson.
— Keith

1
Certo, talvez eu vá realmente pesquisar dictos usando SQLAlchemy e use os benefícios do ORM executando apenas ações de salvamento / atualização.
— Zelid

3

Enquanto a pergunta original remonta há algum tempo, o número de respostas aqui (e minhas próprias experiências) sugerem que é uma pergunta não trivial com muitas abordagens diferentes de complexidade variada com diferentes trade-offs.

É por isso que eu criei a biblioteca SQLAthanor que estende o ORM declarativo do SQLAlchemy com suporte configurável de serialização / desserialização que você pode querer dar uma olhada.

A biblioteca suporta:

  • Python 2.7, 3.4, 3.5 e 3.6.
  • SQLAlchemy versões 0.9 e superior
  • serialização / desserialização de / para JSON, CSV, YAML e Python dict
  • serialização / desserialização de colunas / atributos, relacionamentos, propriedades híbridas e proxies de associação
  • ativar e desativar a serialização para formatos e colunas / relacionamentos / atributos específicos (por exemplo, você deseja suportar um valor de entrada password , mas nunca incluir um valor de saída )
  • processamento de valor de pré-serialização e pós-desserialização (para validação ou coerção de tipo)
  • uma sintaxe bastante direta, que é tanto pitônica quanto perfeitamente consistente com a abordagem do SQLAlchemy

Você pode conferir os documentos abrangentes (espero!) Aqui: https://sqlathanor.readthedocs.io/en/latest

Espero que isto ajude!


2

Serialização e desserialização personalizadas.

"from_json" (método de classe) cria um objeto Model com base nos dados json.

"desserializar" poderia ser chamado apenas na instância e mesclar todos os dados do json na instância do Model.

"serialize" - serialização recursiva

A propriedade __write_only__ é necessária para definir propriedades somente de gravação ("password_hash", por exemplo).

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

Aqui está uma solução que permite selecionar as relações que você deseja incluir em sua saída, tão profundamente quanto você gostaria de ir. NOTA: Esta é uma reescrita completa, usando um dict / str como um argumento em vez de como uma lista. corrige algumas coisas ..

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

por exemplo, usando pessoa / família / lares / quartos ... transformando-o em json, tudo o que você precisa é

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

Acho que é bom colocar apenas na sua classe base para que todos os objetos a tenham. Vou deixar a codificação JSON para você ...
— Tahoe

Note-se que esta versão terá todas as relações lista para ser cauteloso e serviços de relações com uma tonelada de itens ...
— Tahoe

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

Eu pensei em jogar um pouco de golfe com este.

FYI: Estou usando automap_base pois temos um esquema projetado separadamente, de acordo com os requisitos comerciais. Acabei de começar a usar o SQLAlchemy hoje, mas a documentação afirma que automap_base é uma extensão do declarative_base que parece ser o paradigma típico no ORM do SQLAlchemy, então acredito que isso funcione.

Ele não gosta de seguir as chaves estrangeiras de acordo com a solução de Tjorriemorrie , mas simplesmente combina colunas com valores e manipula tipos de Python, str () - usando os valores das colunas. Nossos valores consistem em resultados de tipo de classe Python datetime.time e decimal.Decimal, para que ele faça o trabalho.

Espero que isso ajude qualquer transeunte!


1

Eu sei que este é um post bastante antigo. Tomei solução dada por @SashaB e modificado conforme minha necessidade.

Eu adicionei as seguintes coisas:

  1. Lista de campos ignorados: uma lista de campos a serem ignorados durante a serialização
  2. Lista de substituições de campos: um dicionário que contém nomes de campos a serem substituídos por valores durante a serialização.
  3. Métodos removidos e BaseQuery sendo serializados

Meu código é o seguinte:

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

Espero que ajude alguém!


1

Use o serializador interno no SQLAlchemy:

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

# deserialize object
obj = loads(serialized_obj)

Se você estiver transferindo o objeto entre as sessões, lembre-se de desanexar o objeto da sessão atual usando session.expunge(obj). Para anexá-lo novamente, apenas faça session.add(obj).


Elegante, mas não converte em JSON.
— blakev

2
Para 'serialização' do JSON, consulte marshmallow-sqlalchemy . Definitivamente, a melhor solução quando você expõe objetos a clientes. marshmallow-sqlalchemy.readthedocs.io
— chribsen

O módulo serializador é apropriado apenas para estruturas de consulta. Não é necessário para: instâncias de classes definidas pelo usuário. Eles não contêm referências a mecanismos, sessões ou construções de expressão no caso típico e podem ser serializados diretamente.
— thomasd

1

O código a seguir serializará o resultado sqlalchemy para 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) 

Chamando diversão,

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

1

O AlchemyEncoder é maravilhoso, mas às vezes falha com os valores decimais. Aqui está um codificador aprimorado que resolve o problema decimal -

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

Ao usar o sqlalchemy para conectar-se a um db I, esta é uma solução simples e altamente configurável. Use pandas.

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

0

Em Flask, isso funciona e manipula os campos de data e hora, transformando um campo do tipo
'time': datetime.datetime(2018, 3, 22, 15, 40)em
"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

O serializador embutido engasga com utf-8 não pode decodificar byte de início inválido para algumas entradas. Em vez disso, fui com:

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

Talvez você possa usar uma classe como esta

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!

Com isso todos os objetos têm o to_dictmétodo


0

Ao usar alguns sql bruto e objetos indefinidos, o uso de cursor.descriptionapareceu para obter o que eu estava procurando:

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)

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

2
Despejos de código sem qualquer explicação raramente são úteis. O estouro de pilha é sobre aprendizado, não fornecendo trechos para copiar e colar cegamente. Por favor edite sua pergunta e explicar como ele funciona melhor do que o que o OP fornecido.
— Chris

-2

Minha opinião utilizando dicionários (muitos?):

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

Executando com flask (incluindo jsonify) e flask_sqlalchemy para imprimir saídas como JSON.

Chame a função com jsonify (serialize ()).

Funciona com todas as consultas SQLAlchemy que eu tentei até agora (executando o SQLite3)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.