Bagaimana cara membuat serial hasil SqlAlchemy ke JSON?


192

Django memiliki beberapa serialisasi otomatis yang baik dari model ORM yang dikembalikan dari format DB ke JSON.

Bagaimana cara membuat serialisasi hasil query SQLAlchemy ke format JSON?

Saya mencoba jsonpickle.encodetetapi itu mengkodekan objek permintaan itu sendiri. Saya mencoba json.dumps(items)tetapi kembali

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

Apakah sangat sulit untuk membuat serial objek SQLAlchemy ORM ke JSON / XML? Apakah tidak ada serializer default untuk itu? Ini tugas yang sangat umum untuk membuat serialisasi hasil permintaan ORM saat ini.

Yang saya butuhkan hanyalah mengembalikan JSON atau representasi data XML dari hasil kueri SQLAlchemy.

Hasil query objek SQLAlchemy dalam format JSON / XML diperlukan untuk digunakan dalam javascript datagird (JQGrid http://www.trirand.com/blog/ )


Ini adalah solusi yang bekerja untuk saya. masukkan uraian tautan di sini
octaedro

Jawaban:


129

Implementasi yang datar

Anda dapat menggunakan sesuatu seperti ini:

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)

lalu konversi ke JSON menggunakan:

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

Ini akan mengabaikan bidang yang tidak dapat disandikan (atur ke 'Tidak Ada').

Itu tidak memperluas hubungan secara otomatis (karena ini bisa mengarah pada referensi-sendiri, dan berulang selamanya).

Implementasi rekursif, non-sirkuler

Namun, jika Anda lebih suka mengulang selamanya, Anda dapat menggunakan:

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

Dan kemudian menyandikan objek menggunakan:

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

Ini akan menyandikan semua anak, dan semua anak mereka, dan semua anak mereka ... Berpotensi menyandikan seluruh basis data Anda, pada dasarnya. Ketika mencapai sesuatu yang disandikan sebelumnya, itu akan menyandikannya sebagai 'Tidak Ada'.

Implementasi rekursif, mungkin melingkar, selektif

Alternatif lain, mungkin lebih baik, adalah untuk dapat menentukan bidang yang ingin Anda kembangkan:

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

Anda sekarang dapat menyebutnya dengan:

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

Untuk hanya memperluas bidang SQLAlchemy yang disebut 'parent', misalnya.


itu respons yang bagus, namun saya mendapatkan "tidak dapat menyandikan" BaseQuery "setiap kali ada hubungannya dengan metode non-flat, ada ide?
Ben Kilah

1
@SashaB Bagaimana dengan penargetan yang lebih terperinci terhadap kasus di mana suatu hubungan diulang? Misalnya, jika saya memiliki online_orderdan address, keduanya memiliki hubungan dengan user, tetapi online_orderjuga memiliki hubungan dengan address. Jika saya ingin membuat serial semua ini, saya harus memasukkannya addressdalam fields_to_expand, tapi saya tidak ingin serialisasi secara berlebihan addresskarena hubungannya dengan keduanya userdan online_order.
Chrispy

2
@BenKilah Biarkan saya menebak, Anda menggunakan Flask-SqlAlchemy dan model Anda mewarisi dari db.Model, bukan Base. Jika demikian, modifikasi for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:sehingga terbaca for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:. Ingatlah bahwa solusi ini akan mencegah Anda dari memiliki properti / hubungan dengan nama 'permintaan'
Pakman

cara yang sama seperti yang saya lakukan, tetapi jauh lebih kompleks. stackoverflow.com/questions/7102754/…
tyan

2
Anda dapat menggunakan solusi saya github.com/n0nSmoker/SQLAlchemy-serializer
n0nSmoker

273

Anda bisa menampilkan objek Anda sebagai kamus:

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

Dan kemudian Anda gunakan User.as_dict()untuk membuat serial objek Anda.

Seperti dijelaskan dalam Mengkonversi objek baris sqlalchemy ke python dict


2
@charlax, Bagaimana saya memperbaiki DateTime? Dengan menggunakan ini saya mendapatkan 'datetime.datetime (2013, 3, 22, 16, 50, 11) bukan JSON yang dapat diserialisasi' ketika saya melakukan json.dumps
Asken

1
Itu adalah tanggung jawab JSONEncoderobjek. Anda bisa subklas untuk mendefinisikan encoder Anda sendiri untuk beberapa objek, termasuk datetime. Perhatikan bahwa Flask, misalnya, mendukung penyandian datetime di JSON di luar kotak (dengan versi terbaru).
charlax

3
Jika Anda menggunakan metode "deklaratif" sqlalchemy, Anda dapat menambahkan sesuatu seperti ini ke kelas basis kustom - ini cukup berguna karena Anda dapat memanggil my_orm_object.toDict () pada objek ORM yang Anda miliki. Demikian pula Anda dapat mendefinisikan metode .toJSON () yang menggunakan metode toDict Anda dan pembuat enkode khusus untuk menangani tanggal, gumpalan, dll.
FredL

7
untuk juga mendukung datetime:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
Shoham

1
Ini tidak berfungsi jika variabel kelas Anda tidak sama dengan nama kolom Anda. Adakah yang tahu bagaimana cara mendapatkan nama kelas?
James Burke

55

Anda dapat mengonversi RowProxy ke dict seperti ini:

 d = dict(row.items())

Kemudian serialkan ke JSON (Anda harus menentukan encoder untuk hal-hal seperti datetimenilai) Tidak sulit jika Anda hanya ingin satu catatan (dan bukan hierarki penuh catatan terkait).

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

1
Ini berfungsi untuk kueri sql khusus saya dengan db.engine.connect () sebagai con: rs = con.execute (sql)
JZ.

1
Ini jauh lebih sederhana dan berhasil. Apa perbedaan antara jawaban ini dan jawaban yang diterima?
Sundeep

46

Saya sarankan menggunakan marshmallow . Ini memungkinkan Anda untuk membuat serializers untuk mewakili contoh model Anda dengan dukungan untuk relasi dan objek bersarang.

Berikut adalah contoh terpotong dari dokumen mereka. Mengambil model 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))

Skema marshmallow untuk kelas itu dibangun seperti ini:

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)

... dan digunakan seperti ini:

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

... akan menghasilkan output seperti ini:

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

Lihat Contoh lengkap Labu-SQLAlchemy mereka .

Perpustakaan yang disebut marshmallow-sqlalchemysecara khusus mengintegrasikan SQLAlchemy dan marshmallow. Di perpustakaan itu, skema untuk Authormodel yang dijelaskan di atas terlihat seperti ini:

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

Integrasi ini memungkinkan jenis bidang disimpulkan dari Columnjenis SQLAlchemy .

marshmallow-sqlalchemy di sini.


12
Saya juga menemukan marshmallow-sqlalchemy.readthedocs.io/en/latest yang menyederhanakan pembuatan skema
Foo L

46

Python 3.7+ dan Flask 1.1+ dapat menggunakan paket dataclasses bawaan

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

The /users/rute sekarang akan kembali daftar pengguna.

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

Membuat serialisasi model terkait secara otomatis

@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

Tanggapan dari jsonify(account)akan seperti ini.

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

Timpa JSON Encoder default

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
Ini sepertinya jenis yang tepat sederhana. Apakah ini juga berfungsi untuk deserialisasi?
Ender2050

Anda dapat mengonversi kamus parsed JSON menjadi model menggunakan argumen kata kunci membongkar:data = request.json['user']; user = User(**data)
tom

3
Catatan yang id: int = Columnakan bekerja, tetapi id = Columntidak akan, sepertinya ANDA HARUS mendeklarasikan pengetikan statis untuk json untuk membuat serial bidang, jika tidak, Anda mendapatkan {}objek kosong .
Ambroise Rabier

1
Ini bekerja untuk saya, mengapa ini bukan jawaban yang diterima? Saya telah bermain-main di app_context selama berjam-jam untuk membuatnya bekerja dengan Flask-Marshmallow.
Nick Dat Le

1
Bekerja untuk saya juga. Perhatikan bahwa jika Anda berada di Python 3.6, Anda akan ingin hanya menginstal paket: pipenv install dataclasses. Dan itu akan bekerja dengan baik.
AleksandrH

14

Paket Flask-JsonTools memiliki implementasi kelas JsonSerializableBase Base untuk model Anda.

Pemakaian:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Sekarang Usermodel ini dapat diserialkan secara ajaib.

Jika kerangka kerja Anda bukan Flask, Anda bisa mengambil kode


2
Ini hanya memecahkan setengah dari masalah, karena hanya membuat serialisasi satu baris. Bagaimana cara membuat serial seluruh hasil kueri?
Steve Bennett

@SteveBennett menggunakan jsontools' jsonapi untuk mengkodekan respon. Itu akan secara otomatis menyandikan objek kembali
Tjorriemorrie

Saya memiliki model sqlalchemy yang sangat sederhana, dan saya mendapatkan: TypeError: <ORM. Objek negara di 0x03577A50> bukan JSON yang dapat diserialisasi
Matej

1
Ini akhirnya bekerja dengan secara eksplisit memanggil __json __ () pada objek model saya: return my_object .__ json __ ()
Matej

Perpustakaan tidak berfungsi dengan Flask 1.0 dan di atasnya, karena import flask.ext.whatevertidak lagi didukung di Flask 1.0.
Adarsh ​​Madrecha

14

Untuk alasan keamanan Anda tidak boleh mengembalikan semua bidang model. Saya lebih suka memilihnya secara selektif.

Pengkodean json Flask sekarang mendukung UUID, datetime dan hubungan (dan ditambahkan querydan query_classuntuk db.Modelkelas flask_sqlalchemy ). Saya telah memperbarui encoder sebagai berikut:

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

Dengan ini saya secara opsional dapat menambahkan __json__properti yang mengembalikan daftar bidang yang ingin saya encode:

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

Saya menambahkan @jsonapi ke tampilan saya, mengembalikan daftar hasil dan kemudian output saya adalah sebagai berikut:

[

{

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

]

Cantik! Sekali lagi, buktikan bahwa kadang-kadang Anda tidak memerlukan paket besar untuk setiap tugas kecil yang bodoh - bahwa belajar DSL bisa lebih sulit daripada melakukannya dengan cara yang "sulit". Saya melihat banyak banyak paket JSON dan REST sebelum mendarat di sini. Benar, ini masih memerlukan paket, flask_jsontools (untuk menambahkan @jsonapike @app.routedalam views.py dll), tapi saya menyukai kesederhanaan itu. Saya pikir itu adalah Flask yang murah, tapi tidak ada tanggal jadi saya menambahkannya sendiri ke json_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
juanitogan

10

Anda dapat menggunakan introspeksi SqlAlchemy karena ini:

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

Dapatkan inspirasi dari jawaban di sini: Konversikan objek baris sqlalchemy ke python dict


5

Penjelasan lebih rinci. Dalam model Anda, tambahkan:

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

Ini str()untuk python 3 jadi jika menggunakan python 2 gunakan unicode(). Seharusnya membantu deserialize tanggal. Anda dapat menghapusnya jika tidak berurusan dengan itu.

Anda sekarang dapat meminta basis data seperti ini

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

First()diperlukan untuk menghindari kesalahan aneh. as_dict()sekarang akan deserialize hasilnya. Setelah deserialisasi, siap untuk diubah menjadi json

jsonify(some_result)

3

Ini tidak begitu lurus ke depan. Saya menulis beberapa kode untuk melakukan ini. Saya masih mengerjakannya, dan menggunakan kerangka kerja MochiKit. Ini pada dasarnya menerjemahkan objek majemuk antara Python dan Javascript menggunakan proxy dan konverter JSON terdaftar.

Sisi peramban untuk objek basis data adalah db.js. Diperlukan sumber proxy Python dasar di proxy.js .

Di sisi Python ada modul proxy dasar . Kemudian akhirnya encoder objek SqlAlchemy di webserver.py . Ini juga tergantung pada ekstraktor metadata yang ditemukan dalam file models.py .


Cukup rumit dari pandangan pertama ... Yang saya butuhkan - adalah untuk mendapatkan hasil query objek SQLAlchemy dalam format JSON / XML untuk menggunakannya dalam javascript datagird (JQGrid trirand.com/blog )
Zelid

Kadang-kadang masalah lebih rumit daripada yang Anda temui pada pandangan pertama ... Ini menangani objek yang dikembalikan sebagai kunci asing, dan mencoba menghindari rekursi tak terbatas yang terjadi dengan hubungan yang sangat bersarang. Namun, Anda mungkin dapat menulis beberapa kueri khusus yang hanya mengembalikan tipe dasar dan membuat serialisasi dengan yang sederhana secara langsung.
Keith

1
Benar, mungkin saya benar-benar akan pergi dengan query untuk dikts menggunakan SQLAlchemy dan akan menggunakan manfaat dari ORM melakukan tindakan simpan / perbarui saja.
Zelid

3

Sementara pertanyaan awal kembali beberapa waktu, jumlah jawaban di sini (dan pengalaman saya sendiri) menunjukkan bahwa ini adalah pertanyaan yang tidak sepele dengan banyak pendekatan berbeda dari kompleksitas yang berbeda dengan pengorbanan yang berbeda.

Itu sebabnya saya membangun perpustakaan SQLAthanor yang memperluas ORM deklaratif SQLAlchemy dengan dukungan serialisasi / de-serialisasi yang dapat dikonfigurasi yang mungkin ingin Anda lihat.

Perpustakaan mendukung:

  • Python 2.7, 3.4, 3.5, dan 3.6.
  • SQLAlchemy versi 0.9 dan lebih tinggi
  • serialisasi / de-serialisasi ke / dari JSON, CSV, YAML, dan Python dict
  • serialisasi / de-serialisasi kolom / atribut, hubungan, properti hibrid, dan proksi asosiasi
  • mengaktifkan dan menonaktifkan serialisasi untuk format dan kolom / hubungan / atribut tertentu (mis. Anda ingin mendukung nilai masuk password , tetapi tidak pernah menyertakan yang keluar )
  • pemrosesan nilai pra-serialisasi dan pasca-deserialisasi (untuk validasi atau paksaan jenis)
  • sintaks yang cukup mudah yaitu Pythonic dan konsisten dengan pendekatan SQLAlchemy sendiri

Anda dapat melihat dokumen komprehensif (saya harap!) Di sini: https://sqlathanor.readthedocs.io/en/latest

Semoga ini membantu!


2

Serialisasi dan deserialisasi kustom.

"from_json" (metode kelas) membangun objek Model berdasarkan data json.

"deserialize" hanya bisa dipanggil pada contoh, dan menggabungkan semua data dari json ke dalam Contoh model.

"serialize" - serialisasi rekursif

Properti __write_only__ diperlukan untuk mendefinisikan properti hanya tulis ("password_hash" misalnya).

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

Berikut ini adalah solusi yang memungkinkan Anda memilih hubungan yang ingin Anda sertakan dalam output Anda sedalam yang Anda inginkan. CATATAN: Ini adalah penulisan ulang yang lengkap dengan mengambil dict / str sebagai arg dan bukan daftar. memperbaiki beberapa hal ..

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

jadi untuk contoh menggunakan orang / keluarga / rumah / kamar ... mengubahnya menjadi json yang Anda butuhkan

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

Ini bagus saya pikir hanya memasukkan kelas dasar Anda sehingga semua objek akan memilikinya. Saya akan menyerahkan json encoding kepada Anda ...
tahoe

Perhatikan bahwa versi ini akan mendapatkan semua relasi daftar jadi berhati-hatilah menyediakan relasi dengan satu ton item ...
tahoe

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

Saya pikir saya akan bermain golf kode kecil dengan yang ini.

FYI: Saya menggunakan automap_base karena kami memiliki skema yang dirancang secara terpisah sesuai dengan persyaratan bisnis. Saya baru saja mulai menggunakan SQLAlchemy hari ini tetapi dokumentasi menyatakan bahwa automap_base adalah ekstensi untuk declarative_base yang tampaknya menjadi paradigma khas dalam ORM SQLAlchemy jadi saya percaya ini harus bekerja.

Itu tidak terbiasa dengan mengikuti kunci asing per solusi Tjorriemorrie , tetapi hanya mencocokkan kolom dengan nilai dan menangani tipe Python dengan str () - ing nilai kolom. Nilai-nilai kami terdiri dari Python datetime.time dan desimal. Hasil tipe kelas desimal sehingga menyelesaikan pekerjaan.

Semoga ini bisa membantu orang yang lewat!


1

Saya tahu ini posting yang cukup lama. Saya mengambil solusi yang diberikan oleh @SashaB dan dimodifikasi sesuai kebutuhan saya.

Saya menambahkan hal-hal berikut ke dalamnya:

  1. Daftar abaikan bidang: Daftar bidang yang diabaikan saat membuat serial
  2. Daftar ganti bidang: Kamus yang berisi nama bidang harus diganti dengan nilai saat membuat serial.
  3. Metode yang dihapus dan BaseQuery mendapatkan serial

Kode saya adalah sebagai berikut:

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

Semoga ini bisa membantu seseorang!


1

Gunakan serializer bawaan di SQLAlchemy:

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

# deserialize object
obj = loads(serialized_obj)

Jika Anda mentransfer objek di antara sesi, ingatlah untuk melepaskan objek dari sesi saat ini menggunakan session.expunge(obj). Untuk melampirkannya lagi, lakukan saja session.add(obj).


Bagus, tetapi tidak mengkonversi ke JSON.
blakev

2
Untuk 'serialisasi' JSON, periksa marshmallow-sqlalchemy . Pasti solusi terbaik saat Anda mengekspos objek ke klien. marshmallow-sqlalchemy.readthedocs.io
chribsen

Modul serializer hanya sesuai untuk struktur kueri. Ini tidak diperlukan untuk: instance kelas yang ditentukan pengguna. Ini tidak mengandung referensi ke mesin, sesi atau konstruksi ekspresi dalam kasus khas dan dapat diserialisasi secara langsung.
thomasd

1

kode berikut akan membuat serial hasil sqlalchemy ke 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) 

Menyenangkan,

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

1

AlchemyEncoder luar biasa tetapi terkadang gagal dengan nilai Desimal. Berikut ini adalah pembuat enkode yang disempurnakan yang memecahkan masalah desimal -

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

Saat menggunakan sqlalchemy untuk terhubung ke db, saya ini adalah solusi sederhana yang sangat dapat dikonfigurasi. Gunakan 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
Kesedihan kode tanpa penjelasan apa pun jarang membantu. Stack Overflow adalah tentang belajar, bukan menyediakan cuplikan untuk menyalin dan menempel secara membabi buta. Harap edit pertanyaan Anda dan jelaskan cara kerjanya lebih baik daripada yang diberikan OP.
Chris

0

Di bawah Flask, ini berfungsi dan menangani bidang datatime, mengubah bidang tipe
'time': datetime.datetime(2018, 3, 22, 15, 40)menjadi
"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

Serial serializer bawaan dengan utf-8 tidak dapat mendekode byte awal yang tidak valid untuk beberapa input. Sebaliknya, saya pergi dengan:

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

Mungkin Anda bisa menggunakan kelas seperti ini

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!

Dengan itu semua objek memiliki to_dictmetode


0

Saat menggunakan beberapa sql mentah dan objek tidak terdefinisi, menggunakan cursor.descriptionmuncul untuk mendapatkan apa yang saya cari:

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

Kamus yang saya gunakan (terlalu banyak?):

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

Berjalan dengan flask (termasuk jsonify) dan flask_sqlalchemy untuk mencetak output sebagai JSON.

Panggil fungsi dengan jsonify (serialize ()).

Bekerja dengan semua pertanyaan SQLAlchemy yang telah saya coba sejauh ini (menjalankan SQLite3)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.