Come serializzare i risultati di SqlAlchemy su JSON?


192

Django ha una buona serializzazione automatica dei modelli ORM restituiti dal formato DB a JSON.

Come serializzare il risultato della query SQLAlchemy nel formato JSON?

Ho provato jsonpickle.encodema codifica l'oggetto query stesso. Ho provato json.dumps(items)ma ritorna

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

È davvero così difficile serializzare gli oggetti SQLAlchemy ORM su JSON / XML? Non esiste un serializzatore predefinito per questo? Oggigiorno è compito molto comune serializzare i risultati delle query ORM.

Ciò di cui ho bisogno è solo per restituire la rappresentazione dei dati JSON o XML del risultato della query SQLAlchemy.

Il risultato della query degli oggetti SQLAlchemy in formato JSON / XML è necessario per essere utilizzato nel datagird javascript (JQGrid http://www.trirand.com/blog/ )


Questa è una soluzione alternativa che funziona per me. inserisci qui la descrizione del link
octaedro

Risposte:


129

Un'implementazione piatta

Puoi usare qualcosa del genere:

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 poi converti in JSON usando:

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

Ignorerà i campi non codificabili (impostarli su "Nessuno").

Non si espande automaticamente le relazioni (dal momento che ciò potrebbe portare ad auto-riferimenti e ripetersi per sempre).

Un'implementazione ricorsiva e non circolare

Se, tuttavia, preferisci un ciclo continuo, puoi usare:

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 quindi codificare gli oggetti usando:

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

Questo codificherebbe tutti i bambini, tutti i loro figli e tutti i loro figli ... Potenzialmente codificherebbe l'intero database. Quando raggiunge qualcosa che è stato codificato prima, lo codificherà come "Nessuno".

Un'implementazione ricorsiva, possibilmente circolare, selettiva

Un'altra alternativa, probabilmente migliore, è quella di poter specificare i campi che si desidera espandere:

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

Ora puoi chiamarlo con:

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

Per esempio, per espandere solo i campi SQLAlchemy chiamati "genitori".


è un'ottima risposta, comunque ottengo un "impossibile codificare" BaseQuery "ogni volta che colpisce una relazione con i metodi non piatti, qualche idea?
Ben Kilah,

1
@SashaB Che ne dici di puntare in modo più granulare contro i casi in cui una relazione si ripete? Ad esempio, se ho online_ordere address, entrambi con una relazione user, ma online_orderha anche una relazione con address. Se volessi serializzare tutto questo, dovrei includerlo addressin fields_to_expand, ma non vorrei serializzare in modo ridondante a addresscausa della sua relazione con entrambi usere online_order.
Chrispy,

2
@BenKilah Fammi indovinare, stai usando Flask-SqlAlchemy e i tuoi modelli ereditano da db.Model, non da Base. In tal caso, modifica in for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:modo che legga for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:. Tieni presente che questa soluzione ti impedirà di avere una proprietà / relazione con il nome 'query'
Pakman

come ho fatto io, ma molto più complesso. stackoverflow.com/questions/7102754/...
Tyan


273

Potresti semplicemente generare il tuo oggetto come un dizionario:

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

E poi usi User.as_dict() per serializzare il tuo oggetto.

Come spiegato in Converti l'oggetto riga sqlalchemy in python dict


2
@charlax, Come ho riparato un DateTime? Usando questo ottengo "datetime.datetime (2013, 3, 22, 16, 50, 11) non è serializzabile JSON" quando eseguo json.dumps
Asken,

1
È la responsabilità JSONEncoderdell'oggetto. Puoi sottoclassarlo per definire il tuo codificatore per alcuni oggetti, incluso il datetime. Si noti che Flask, ad esempio, supporta la codifica del datetime in JSON immediatamente (con l'ultima versione).
charlax,

3
Se usi il metodo "dichiarativo" di sqlalchemy puoi aggiungere qualcosa del genere a una classe Base personalizzata - questo è abbastanza utile come puoi quindi chiamare my_orm_object.toDict () su qualsiasi oggetto ORM che hai. Allo stesso modo è possibile definire un metodo .toJSON () che utilizza il metodo toDict e un codificatore personalizzato per la gestione di date,
BLOB

7
per supportare anche il datetime:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
Shoham,

1
Questo non funziona se le variabili di classe non sono uguali ai nomi delle colonne. Qualche idea su come ottenere i nomi delle classi invece?
James Burke,

55

Puoi convertire un RowProxy in un dict come questo:

 d = dict(row.items())

Quindi serializza quello su JSON (dovrai specificare un codificatore per cose come i datetimevalori) Non è così difficile se vuoi solo un record (e non una gerarchia completa di record correlati).

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

1
Questo funziona per la mia query sql personalizzata con db.engine.connect () come con: rs = con.execute (sql)
JZ.

1
Questo è molto più semplice e funziona. Qual è la differenza tra questa risposta e la risposta accettata?
Sundeep

46

Consiglio di usare marshmallow . Consente di creare serializzatori per rappresentare le istanze del modello con supporto per relazioni e oggetti nidificati.

Ecco un esempio troncato dai loro documenti. Prendere il modello 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))

Uno schema marshmallow per quella classe è costruito in questo modo:

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 usato così:

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

... produrrebbe un output come questo:

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

Dai un'occhiata al loro esempio completo di Flask-SQLAlchemy .

Una libreria chiamata marshmallow-sqlalchemyintegra specificamente SQLAlchemy e marshmallow. In quella libreria, lo schema per il Authormodello sopra descritto è simile al seguente:

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

L'integrazione consente di dedurre i tipi di campo dai Columntipi SQLAlchemy .

marshmallow-sqlalchemy qui.


12
Ho anche trovato marshmallow-sqlalchemy.readthedocs.io/en/latest che semplifica la generazione dello schema
Foo L

46

Python 3.7+ e Flask 1.1+ possono utilizzare il pacchetto di dataclass incorporato

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

Il /users/percorso ora restituirà un elenco di utenti.

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

Serializzazione automatica dei modelli correlati

@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

La risposta jsonify(account)sarebbe questa.

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

Sovrascrivi il codificatore JSON predefinito

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
Sembra il giusto tipo di semplice. Funziona anche per la deserializzazione?
Ender2050

È possibile convertire un dizionario di JSON analizzato in un modello utilizzando l'argomento della parola chiave unpacking:data = request.json['user']; user = User(**data)
tom

3
Nota che id: int = Columnfunzionerà, ma id = Columnnon funzionerà, sembra che DEVI dichiarare la tipizzazione statica per il json per serializzare il campo, altrimenti otterrai un {}oggetto vuoto .
Ambroise Rabier,

1
Questo ha funzionato per me, perché non è questa la risposta accettata? Ho giocato a app_context per ore per farlo funzionare con Flask-Marshmallow.
Nick Dat Le

1
Ha funzionato anche per me. Si noti che se si è in Python 3.6, ti consigliamo di installare solo il pacchetto: pipenv install dataclasses. E poi funzionerà bene.
AleksandrH,

14

Il pacchetto Flask-JsonTools ha un'implementazione della classe Base JsonSerializableBase per i tuoi modelli.

Uso:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Ora il Usermodello è magicamente serializzabile.

Se il tuo framework non è Flask, puoi semplicemente prendere il codice


2
Questo risolve solo metà del problema, poiché serializza solo una singola riga. Come serializzare l'intero risultato della query?
Steve Bennett,

@SteveBennett usa il jsonapi dei jsontools per codificare la risposta. Ciò codificherà automaticamente l'oggetto di ritorno
Tjorriemorrie il

Ho un modello sqlalchemy molto semplice e sto ottenendo: TypeError: <oggetto ORM.State a 0x03577A50> non è serializzabile JSON
Matej

1
Alla fine ha funzionato chiamando esplicitamente __json __ () sul mio oggetto modello: return my_object .__ json __ ()
Matej

La libreria non funziona con Flask 1.0 e versioni successive, poiché import flask.ext.whatevernon è più supportata in Flask 1.0.
Adarsh ​​Madrecha,

14

Per motivi di sicurezza non è mai necessario restituire tutti i campi del modello. Preferisco sceglierli selettivamente.

La codifica json di Flask ora supporta UUID, datetime e relazioni (e aggiunti querye query_classper la db.Modelclasse flask_sqlalchemy ). Ho aggiornato l'encoder come segue:

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

Con questo posso opzionalmente aggiungere una __json__proprietà che restituisce l'elenco dei campi che desidero codificare:

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

Aggiungo @jsonapi alla mia vista, restituisco la lista dei risultati e quindi il mio output è il seguente:

[

{

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

]

Bellissimo! Ancora una volta, prova che a volte non hai bisogno di un grosso pacchetto per ogni stupido piccolo compito - che imparare il DSL può essere più difficile che farlo nel modo "difficile". Ho guardato molti molti pacchetti JSON e REST prima di atterrare qui. È vero, questo richiede ancora un pacchetto, flask_jsontools (da aggiungere @jsonapia @app.routein views.py ecc.), Ma adoro la sua semplicità. Penso che sia economico Flask aggiunto datetime ma non data quindi l'ho aggiunto io stesso a json_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
juanitogan

10

È possibile utilizzare l'introspezione di SqlAlchemy come questo:

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

Lasciati ispirare da una risposta qui: converti l'oggetto riga sqlalchemy in python dict


5

Una spiegazione più dettagliata. Nel tuo modello, aggiungi:

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

Il str()è per pitone 3 quindi se utilizza pitone 2 uso unicode(). Dovrebbe aiutare a deserializzare le date. Puoi rimuoverlo se non hai a che fare con quelli.

Ora puoi eseguire una query sul database in questo modo

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

First()è necessario per evitare strani errori. as_dict()ora deserializzerà il risultato. Dopo la deserializzazione, è pronto per essere trasformato in json

jsonify(some_result)

3

Non è così diretto. Ho scritto del codice per farlo. Ci sto ancora lavorando e utilizza il framework MochiKit. In pratica traduce oggetti composti tra Python e Javascript usando un proxy e convertitori JSON registrati.

Il lato browser per gli oggetti del database è db.js È necessario l'origine proxy Python di base in proxy.js .

Sul lato Python c'è il modulo proxy di base . Quindi finalmente il codificatore di oggetti SqlAlchemy in webserver.py . Dipende anche dagli estrattori di metadati presenti nel file models.py .


Abbastanza complicato dalla prima occhiata ... Quello di cui ho bisogno - è ottenere il risultato della query degli oggetti SQLAlchemy in formato JSON / XML per usarlo nel datagird javascript (JQGrid trirand.com/blog )
Zelid

A volte i problemi sono più complicati di quanto si pensi a prima vista ... Questo gestisce gli oggetti restituiti come chiavi esterne e cerca di evitare l'infinita ricorsione che si verifica con relazioni profondamente annidate. Tuttavia, potresti probabilmente scrivere alcune query personalizzate che restituiscono solo tipi di base e serializzano direttamente quelle con simplejson.
Keith,

1
Bene, forse andrò davvero a fare query per dicts usando SQLAlchemy e userò i vantaggi di ORM eseguendo solo azioni di salvataggio / aggiornamento.
Zelid,

3

Mentre la domanda originale risale a un po 'di tempo, il numero di risposte qui (e le mie esperienze personali) suggeriscono che è una domanda non banale con molti approcci diversi di varia complessità con diversi compromessi.

Ecco perché ho creato SQLAthanor libreria che estende l'ORM dichiarativo di SQLAlchemy con supporto configurabile di serializzazione / deserializzazione che potresti voler dare un'occhiata.

La libreria supporta:

  • Python 2.7, 3.4, 3.5 e 3.6.
  • SQLAlchemy versioni 0.9 e successive
  • serializzazione / deserializzazione su / da JSON, CSV, YAML e Python dict
  • serializzazione / deserializzazione di colonne / attributi, relazioni, proprietà ibride e proxy di associazione
  • abilitazione e disabilitazione della serializzazione per particolari formati e colonne / relazioni / attributi (ad es. si desidera supportare un valore in entrata password , ma non includerne mai uno in uscita )
  • elaborazione del valore di pre-serializzazione e post-deserializzazione (per convalida o coercizione del tipo)
  • una sintassi piuttosto semplice che è sia pitonica che perfettamente coerente con l'approccio di SQLAlchemy

Puoi consultare la documentazione completa (spero!) Qui: https://sqlathanor.readthedocs.io/en/latest

Spero che questo ti aiuti!


2

Serializzazione e deserializzazione personalizzate.

"from_json" (metodo di classe) crea un oggetto Model basato su dati json.

"deserialize" potrebbe essere chiamato solo sull'istanza e unire tutti i dati da JSON nell'istanza del modello.

"serialize" - serializzazione ricorsiva

La proprietà __write_only__ è necessaria per definire le proprietà di sola scrittura (ad esempio "password_hash").

class Serializable(object):
    __exclude__ = ('id',)
    __include__ = ()
    __write_only__ = ()

    @classmethod
    def from_json(cls, json, selfObj=None):
        if selfObj is None:
            self = cls()
        else:
            self = selfObj
        exclude = (cls.__exclude__ or ()) + Serializable.__exclude__
        include = cls.__include__ or ()
        if json:
            for prop, value in json.iteritems():
                # ignore all non user data, e.g. only
                if (not (prop in exclude) | (prop in include)) and isinstance(
                        getattr(cls, prop, None), QueryableAttribute):
                    setattr(self, prop, value)
        return self

    def deserialize(self, json):
        if not json:
            return None
        return self.__class__.from_json(json, selfObj=self)

    @classmethod
    def serialize_list(cls, object_list=[]):
        output = []
        for li in object_list:
            if isinstance(li, Serializable):
                output.append(li.serialize())
            else:
                output.append(li)
        return output

    def serialize(self, **kwargs):

        # init write only props
        if len(getattr(self.__class__, '__write_only__', ())) == 0:
            self.__class__.__write_only__ = ()
        dictionary = {}
        expand = kwargs.get('expand', ()) or ()
        prop = 'props'
        if expand:
            # expand all the fields
            for key in expand:
                getattr(self, key)
        iterable = self.__dict__.items()
        is_custom_property_set = False
        # include only properties passed as parameter
        if (prop in kwargs) and (kwargs.get(prop, None) is not None):
            is_custom_property_set = True
            iterable = kwargs.get(prop, None)
        # loop trough all accessible properties
        for key in iterable:
            accessor = key
            if isinstance(key, tuple):
                accessor = key[0]
            if not (accessor in self.__class__.__write_only__) and not accessor.startswith('_'):
                # force select from db to be able get relationships
                if is_custom_property_set:
                    getattr(self, accessor, None)
                if isinstance(self.__dict__.get(accessor), list):
                    dictionary[accessor] = self.__class__.serialize_list(object_list=self.__dict__.get(accessor))
                # check if those properties are read only
                elif isinstance(self.__dict__.get(accessor), Serializable):
                    dictionary[accessor] = self.__dict__.get(accessor).serialize()
                else:
                    dictionary[accessor] = self.__dict__.get(accessor)
        return dictionary

2

Ecco una soluzione che ti consente di selezionare le relazioni che desideri includere nel tuo output nel modo più approfondito che desideri. NOTA: questa è una riscrittura completa che prende un dict / str come arg anziché come un elenco. risolve alcune cose ..

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

quindi per esempio usando persona / famiglia / case / stanze ... trasformandolo in json tutto ciò di cui hai bisogno è

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

Va bene, penso di inserire semplicemente la tua classe base in modo che tutti gli oggetti la abbiano. Lascio a te la codifica json ...
Tahoe,

Nota che questa versione otterrà tutte le relazioni dell'elenco, quindi sii cauto nel fornire relazioni con un sacco di articoli ...
Tahoe,

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

Ho pensato di giocare a golf con questo codice.

Cordiali saluti: Sto usando automap_base poiché abbiamo uno schema progettato separatamente in base alle esigenze aziendali. Ho appena iniziato a utilizzare SQLAlchemy oggi, ma la documentazione afferma che automap_base è un'estensione di dichiarative_base che sembra essere il tipico paradigma nell'ORM di SQLAlchemy, quindi credo che dovrebbe funzionare.

Non ha nessuna fantasia con le seguenti chiavi esterne per la soluzione di Tjorriemorrie , ma abbina semplicemente le colonne ai valori e gestisce i tipi di Python str () - i valori delle colonne. I nostri valori sono costituiti da Python datetime.time e decimal.Decimal tipo di classe risultati in modo da portare a termine il lavoro.

Spero che questo aiuti tutti i passanti!


1

So che questo è un post piuttosto vecchio. Ho preso la soluzione fornita da @SashaB e modificata secondo le mie necessità.

Ho aggiunto le seguenti cose ad esso:

  1. Elenco ignora campo: un elenco di campi da ignorare durante la serializzazione
  2. Elenco di sostituzione dei campi: un dizionario contenente nomi di campi da sostituire con valori durante la serializzazione.
  3. Metodi rimossi e BaseQuery vengono serializzati

Il mio codice è il seguente:

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

Spero che aiuti qualcuno!


1

Utilizzare il serializzatore integrato in SQLAlchemy:

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

# deserialize object
obj = loads(serialized_obj)

Se si trasferisce l'oggetto tra le sessioni, ricordarsi di staccare l'oggetto dalla sessione corrente utilizzando session.expunge(obj). Per collegarlo di nuovo, basta farlo session.add(obj).


Nifty, ma non converte in JSON.
Blakev,

2
Per la "serializzazione" di JSON, controlla marshmallow-sqlalchemy . Sicuramente la soluzione migliore quando si espongono oggetti ai client. marshmallow-sqlalchemy.readthedocs.io
chribsen

Il modulo serializzatore è appropriato solo per le strutture di query. Non è necessario per: istanze di classi definite dall'utente. Questi non contengono riferimenti a motori, sessioni o costrutti di espressione nel caso tipico e possono essere serializzati direttamente.
Thomas

1

il codice seguente serializzerà il risultato sqlalchemy su 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) 

Divertente

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

1

AlchemyEncoder è meraviglioso ma a volte non riesce con valori decimali. Ecco un codificatore migliorato che risolve il problema decimale -

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

Quando si utilizza sqlalchemy per connettersi a un db I questa è una soluzione semplice che è altamente configurabile. Usa i panda.

import pandas as pd
import sqlalchemy

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

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

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

  return sql_json

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

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

step3:
return jsonify(list)

3
I dump del codice senza alcuna spiegazione sono raramente utili. Stack Overflow riguarda l'apprendimento, non fornire frammenti da copiare e incollare alla cieca. Si prega di modificare la tua domanda e spiegare come funziona meglio di quello che l'OP fornito.
Chris,

0

Sotto Flask, funziona e gestisce i campi di data, trasformando un campo di tipo
'time': datetime.datetime(2018, 3, 22, 15, 40)in
"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

Il serializzatore incorporato soffoca con utf-8 non può decodificare byte di avvio non validi per alcuni ingressi. Invece, sono andato con:

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

Forse puoi usare una classe come questa

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!

Con ciò tutti gli oggetti hanno il to_dictmetodo


0

Durante l'utilizzo di alcuni sql grezzi e oggetti non definiti, l'utilizzo è cursor.descriptionapparso per ottenere ciò che stavo cercando:

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

-2

Il mio take utilizza (troppi?) Dizionari:

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

In esecuzione con pallone (incluso jsonify) e flask_sqlalchemy per stampare output come JSON.

Chiamare la funzione con jsonify (serialize ()).

Funziona con tutte le query SQLAlchemy che ho provato finora (eseguendo SQLite3)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.