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.