Làm cách nào để tuần tự hóa kết quả SqlAlchemy thành JSON?


189

Django có một số tuần tự hóa tốt các mô hình ORM được trả về từ định dạng DB sang JSON.

Làm cách nào để tuần tự hóa kết quả truy vấn SQLAlchemy sang định dạng JSON?

Tôi đã thử jsonpickle.encodenhưng nó mã hóa chính đối tượng truy vấn. Tôi đã thử json.dumps(items)nhưng nó trở lại

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

Có thực sự khó khăn khi tuần tự hóa các đối tượng ORM SQLAlchemy thành JSON / XML không? Không có serializer mặc định cho nó? Ngày nay, nhiệm vụ rất phổ biến là tuần tự hóa kết quả truy vấn ORM.

Những gì tôi cần chỉ là trả về biểu diễn dữ liệu JSON hoặc XML của kết quả truy vấn SQLAlchemy.

Kết quả truy vấn đối tượng SQLAlchemy theo định dạng JSON / XML là cần thiết để được sử dụng trong kho dữ liệu javascript (JQGrid http://www.trirand.com/blog/ )


Đây là một cách giải quyết phù hợp với tôi. nhập mô tả liên kết tại đây
octaedro

Câu trả lời:


127

Một triển khai căn hộ

Bạn có thể sử dụng một cái gì đó như thế này:

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)

và sau đó chuyển đổi sang JSON bằng cách sử dụng:

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

Nó sẽ bỏ qua các trường không được mã hóa (đặt chúng thành 'Không').

Nó không tự động mở rộng quan hệ (vì điều này có thể dẫn đến tự tham chiếu và lặp lại mãi mãi).

Thực hiện đệ quy, không tuần hoàn

Tuy nhiên, nếu bạn muốn lặp lại mãi mãi, bạn có thể sử dụng:

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

Và sau đó mã hóa các đối tượng bằng cách sử dụng:

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

Điều này sẽ mã hóa tất cả trẻ em, và tất cả trẻ em của chúng, và tất cả trẻ em của chúng ... Về cơ bản, mã hóa toàn bộ cơ sở dữ liệu của bạn. Khi nó đạt được thứ gì đó được mã hóa trước đó, nó sẽ mã hóa thành 'Không'.

Một cách thực hiện đệ quy, có thể thông tư, chọn lọc

Một cách khác, có lẽ tốt hơn, là có thể chỉ định các trường bạn muốn mở rộng:

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

Bây giờ bạn có thể gọi nó với:

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

Ví dụ, để chỉ mở rộng các trường SQLAlchemy được gọi là 'cha mẹ'.


đó là một phản hồi tuyệt vời, tuy nhiên tôi nhận được "không thể mã hóa" BaseQuery "bất cứ khi nào nó đạt được mối quan hệ với các phương thức không phẳng, có ý tưởng nào không?
Ben Kilah

1
@SashaB Làm thế nào về việc nhắm mục tiêu chi tiết hơn đối với các trường hợp mối quan hệ được lặp lại? Ví dụ, nếu tôi có online_orderaddress, cả hai đều có mối quan hệ user, nhưng online_ordercũng có mối quan hệ address. Nếu tôi muốn tuần tự hóa tất cả những điều này, tôi phải đưa addressvào fields_to_expand, nhưng tôi sẽ không muốn tuần tự hóa một cách dư thừa addressdo mối quan hệ của nó với cả hai useronline_order.
Chrispy

2
@BenKilah Hãy để tôi đoán, bạn đang sử dụng Flask-SqlAlchemy và các mô hình của bạn đang kế thừa từ db.Model, không phải Base. Nếu đó là trường hợp, sửa đổi for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:để nó đọc for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:. Hãy ghi nhớ giải pháp này sẽ ngăn bạn có tài sản / mối quan hệ với tên 'truy vấn'
Pakman

giống như tôi đã làm, nhưng phức tạp hơn nhiều. stackoverflow.com/questions/7102754/ cường
tyan

2
Bạn có thể sử dụng giải pháp của tôi github.com/n0nSmoker/QueryAlchemy-serializer
n0nSmoker

269

Bạn chỉ có thể xuất đối tượng của mình dưới dạng từ điển:

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

Và sau đó bạn sử dụng User.as_dict()để tuần tự hóa đối tượng của bạn.

Như đã giải thích trong Chuyển đổi đối tượng hàng sqlalchemy thành python dict


2
@charlax, Làm cách nào để sửa DateTime? Bằng cách sử dụng này, tôi nhận được 'datetime.datetime (2013, 3, 22, 16, 50, 11) không phải là tuần tự hóa JSON' khi tôi thực hiện json.dumps
Asken

1
Đó là trách nhiệm của JSONEncoderđối tượng. Bạn có thể phân lớp nó để xác định bộ mã hóa của riêng bạn cho một số đối tượng, bao gồm cả datetime. Lưu ý rằng Flask, ví dụ, hỗ trợ mã hóa datetime trong JSON ra khỏi hộp (với phiên bản mới nhất).
charlax

3
Nếu bạn sử dụng phương thức "khai báo" của sqlalchemy, bạn có thể thêm một cái gì đó như thế này vào một lớp Cơ sở tùy chỉnh - điều này khá tiện lợi khi bạn có thể gọi my_orm_object.toDict () trên bất kỳ đối tượng ORM nào bạn có. Tương tự, bạn có thể định nghĩa phương thức .toJSON () sử dụng phương thức toDict của bạn và bộ mã hóa tùy chỉnh để xử lý ngày, các đốm màu, v.v.
FredL

7
để hỗ trợ datetime:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
Shoham

1
Điều này không hoạt động nếu các biến lớp của bạn không giống với tên cột của bạn. Bất kỳ ý tưởng làm thế nào để có được tên lớp thay thế?
James Burke

54

Bạn có thể chuyển đổi RowProxy thành một dict như thế này:

 d = dict(row.items())

Sau đó tuần tự hóa nó thành JSON (bạn sẽ phải chỉ định một bộ mã hóa cho những thứ như datetimegiá trị) Sẽ không khó nếu bạn chỉ muốn một bản ghi (và không phải là một hệ thống phân cấp đầy đủ của các bản ghi liên quan).

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

1
Điều này hoạt động cho truy vấn sql tùy chỉnh của tôi với db.engine.connect () là con: rs = con.execute (sql)
JZ.

1
Điều này là đơn giản hơn nhiều và làm việc. Sự khác biệt giữa câu trả lời này và câu trả lời được chấp nhận là gì?
Sundeep

46

Tôi khuyên bạn nên sử dụng marshmallow . Nó cho phép bạn tạo các bộ nối tiếp để thể hiện các thể hiện mô hình của bạn với sự hỗ trợ cho các mối quan hệ và các đối tượng lồng nhau.

Đây là một ví dụ rút gọn từ tài liệu của họ. Lấy mô hình 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))

Một lược đồ marshmallow cho lớp đó được xây dựng như thế này:

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)

... và được sử dụng như thế này:

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

... sẽ tạo ra một đầu ra như thế này:

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

Hãy xem ví dụ Flask-SQLAlchemy đầy đủ của họ .

Một thư viện được gọi là marshmallow-sqlalchemytích hợp đặc biệt SQLAlchemy và marshmallow. Trong thư viện đó, lược đồ cho Authormô hình được mô tả ở trên trông như thế này:

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

Việc tích hợp cho phép các kiểu trường được suy ra từ SQLAlchemy Column loại .

marshmallow-sqlalchemy ở đây.


12
Tôi cũng tìm thấy marshmallow-sqlalchemy.readthedocs.io/en/latest giúp đơn giản hóa việc tạo lược đồ
Foo L

40

Python 3.7+ và Flask 1.1+ có thể sử dụng gói dataclass tích hợp

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

Các /users/tuyến đường bây giờ sẽ trả về một danh sách người dùng.

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

Tự động nối tiếp các mô hình liên quan

@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

Phản hồi từ đây jsonify(account)sẽ là.

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

Ghi đè Bộ mã hóa JSON mặc định

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
Điều này có vẻ như đúng loại đơn giản. Nó cũng hoạt động cho khử lưu huỳnh?
Ender2050

Bạn có thể chuyển đổi một từ điển JSON được phân tích cú pháp thành một mô hình bằng cách giải nén đối số từ khóa:data = request.json['user']; user = User(**data)
tom

3
Lưu ý rằng nó id: int = Columnsẽ hoạt động, nhưng id = Columnsẽ không, có vẻ như BẠN PHẢI khai báo kiểu gõ tĩnh cho json để tuần tự hóa trường, nếu không bạn sẽ nhận được một {}đối tượng trống .
Ambroise Rabier

1
Điều này làm việc cho tôi, tại sao đây không phải là câu trả lời được chấp nhận? Tôi đã chơi xung quanh app_context trong nhiều giờ để làm cho nó hoạt động với Flask-Marshmallow.
Nick Đạt Lê

1
Làm việc cho tôi là tốt. Lưu ý rằng nếu bạn đang dùng Python 3.6, bạn sẽ chỉ muốn cài đặt gói : pipenv install dataclasses. Và sau đó nó sẽ hoạt động tốt.
AlexanderrH

14

Gói Flask-JsonTools có triển khai JsonSerializableBase lớp Cơ sở cho các mô hình của bạn.

Sử dụng:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

Bây giờ Usermô hình là tuần tự kỳ diệu.

Nếu khung của bạn không phải là Flask, bạn có thể lấy mã


2
Điều này chỉ giải quyết được một nửa vấn đề, vì nó chỉ tuần tự hóa một hàng duy nhất. Làm thế nào để tuần tự hóa toàn bộ kết quả truy vấn?
Steve Bennett

@SteveBennett sử dụng jsonapi của jsontools để mã hóa phản hồi. Điều đó sẽ tự động mã hóa đối tượng trả về
Tjorriemorrie

Tôi có một mô hình sqlalchemy rất đơn giản và tôi đang nhận được: TypeError: <ORM.State object tại 0x03577A50> không phải là tuần tự hóa JSON
Matej

1
Cuối cùng nó cũng hoạt động bằng cách gọi rõ ràng __json __ () trên đối tượng mô hình của tôi: return my_object .__ json __ ()
Matej

Thư viện không hoạt động với Flask 1.0 trở lên, vì import flask.ext.whateverkhông còn được hỗ trợ trong Flask 1.0.
Adarsh ​​Madrecha

14

Vì lý do bảo mật, bạn không bao giờ nên trả lại tất cả các trường của mô hình. Tôi thích chọn lọc chọn chúng.

Mã hóa json của Flask hiện hỗ trợ UUID, datetime và các mối quan hệ (và được thêm vào queryquery_classcho db.Modellớp jar_sqlalchemy ). Tôi đã cập nhật bộ mã hóa như sau:

ứng dụng / 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

Với điều này, tôi có thể tùy chọn thêm một thuộc __json__tính trả về danh sách các trường tôi muốn mã hóa:

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

Tôi thêm @jsonapi vào chế độ xem của mình, trả về danh sách kết quả và sau đó đầu ra của tôi như sau:

[

{

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

]

Xinh đẹp! Một lần nữa, bằng chứng là đôi khi bạn không cần một gói chất béo cho mọi nhiệm vụ nhỏ ngu ngốc - rằng học DSL có thể khó hơn làm theo cách "khó". Tôi đã xem xét nhiều gói JSON và REST trước khi hạ cánh ở đây. Đúng, điều này vẫn đòi hỏi một gói, jar_jsontools (để thêm @jsonapivào @app.routetrong view.py, v.v.), nhưng tôi thích sự đơn giản của nó. Tôi nghĩ rằng đó là giá rẻ Flask thêm datetime nhưng không cập nhật để tôi thêm nó bản thân mình để json_encoder.py : value=...^ if isinstance(value, date):^ data[field] = datetime.combine(value, time.min).isoformat()^ else:^try:...
juanitogan

10

Bạn có thể sử dụng hướng nội của SqlAlchemy như sau:

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

Lấy cảm hứng từ một câu trả lời ở đây: Chuyển đổi đối tượng hàng sqlalchemy thành python dict


5

Một lời giải thích chi tiết hơn. Trong mô hình của bạn, thêm:

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

Cái str()này dành cho python 3 nên nếu dùng python 2 thì dùng unicode(). Nó sẽ giúp khử ngày tháng. Bạn có thể loại bỏ nó nếu không đối phó với những người.

Bây giờ bạn có thể truy vấn cơ sở dữ liệu như thế này

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

First()là cần thiết để tránh các lỗi lạ. as_dict()bây giờ sẽ giải trừ kết quả. Sau khi khử lưu huỳnh, nó đã sẵn sàng để chuyển sang json

jsonify(some_result)

3

Nó không quá căng thẳng. Tôi đã viết một số mã để làm điều này. Tôi vẫn đang làm việc với nó và nó sử dụng khung MochiKit. Về cơ bản, nó dịch các đối tượng hỗn hợp giữa Python và Javascript bằng proxy và các trình chuyển đổi JSON đã đăng ký.

Phía trình duyệt cho các đối tượng cơ sở dữ liệu là db.js Nó cần nguồn proxy Python cơ bản trong proxy.js .

Về phía Python có mô-đun proxy cơ sở . Cuối cùng, bộ mã hóa đối tượng SqlAlchemy trong webserver.py . Nó cũng phụ thuộc vào nhổ siêu dữ liệu tìm thấy trong models.py tập tin.


Khá phức tạp từ cái nhìn đầu tiên ... Những gì tôi cần - là để có được SQLAlchemy đối tượng kết quả truy vấn trong JSON / định dạng XML để sử dụng nó trong datagird javascript (jqGrid trirand.com/blog )
Zelid

Đôi khi các vấn đề phức tạp hơn so với bạn thoạt nhìn ... Điều này xử lý các đối tượng được trả lại dưới dạng khóa ngoại và cố gắng tránh đệ quy vô hạn xảy ra với các mối quan hệ lồng nhau sâu sắc. Tuy nhiên, bạn có thể có thể viết một số truy vấn tùy chỉnh chỉ trả về các loại cơ sở và tuần tự hóa các truy vấn với Simplejson trực tiếp.
Keith

1
Đúng vậy, có lẽ tôi sẽ thực sự truy vấn các câu lệnh bằng cách sử dụng SQLAlchemy và sẽ chỉ sử dụng các lợi ích của ORM khi thực hiện các hành động lưu / cập nhật.
Zelid

3

Trong khi câu hỏi ban đầu trở lại một lúc, số lượng câu trả lời ở đây (và kinh nghiệm của riêng tôi) cho thấy đây là một câu hỏi không tầm thường với nhiều cách tiếp cận khác nhau về độ phức tạp khác nhau với sự đánh đổi khác nhau.

Đó là lý do tại sao tôi xây dựng SQLAthanor thư viện mở rộng ORM khai báo của SQLAlchemy với hỗ trợ tuần tự hóa / khử tuần tự hóa có thể định cấu hình mà bạn có thể muốn xem qua.

Thư viện hỗ trợ:

  • Python 2.7, 3.4, 3.5 và 3.6.
  • Phiên bản SQLAlchemy 0.9 trở lên
  • tuần tự hóa / khử tuần tự hóa đến / từ JSON, CSV, YAML và Python dict
  • tuần tự hóa / khử tuần tự hóa các cột / thuộc tính, mối quan hệ, thuộc tính lai và proxy kết hợp
  • cho phép và vô hiệu hóa tuần tự hóa cho các định dạng và cột / mối quan hệ / thuộc tính cụ thể (ví dụ: bạn muốn hỗ trợ giá trị gửi đến password , nhưng không bao giờ bao gồm giá trị bên ngoài )
  • tiền xử lý tuần tự hóa và xử lý sau khử lưu huỳnh (để xác thực hoặc ép buộc kiểu)
  • một cú pháp khá đơn giản, vừa phù hợp với Pythonic vừa phù hợp với cách tiếp cận của SQLAlchemy

Bạn có thể xem tài liệu toàn diện (tôi hy vọng!) Tại đây: https://sqlathanor.readthedocs.io/en/latest

Hi vọng điêu nay co ich!


2

Tùy chỉnh tuần tự hóa và giải tuần tự hóa.

"from_json" (phương thức lớp) xây dựng một đối tượng Model dựa trên dữ liệu json.

"deserialize" chỉ có thể được gọi trong trường hợp và hợp nhất tất cả dữ liệu từ json vào thể hiện Model.

"serialization" - serialization đệ quy

Thuộc tính __write_only__ là cần thiết để xác định các thuộc tính chỉ ghi (ví dụ "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

Đây là một giải pháp cho phép bạn chọn các mối quan hệ bạn muốn đưa vào đầu ra của bạn sâu như bạn muốn đi. LƯU Ý: Đây là một bản viết lại hoàn chỉnh lấy một dict / str làm đối số chứ không phải là một danh sách. sửa một số thứ ..

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

vì vậy, ví dụ sử dụng người / gia đình / nhà / phòng ... biến nó thành json tất cả những gì bạn cần là

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

Điều này là tốt, tôi nghĩ chỉ cần đặt trong lớp cơ sở của bạn để tất cả các đối tượng sẽ có nó. Tôi sẽ để lại mã hóa json cho bạn ...
tahoe

Lưu ý rằng phiên bản này sẽ có được tất cả các mối quan hệ danh sách, vì vậy hãy thận trọng khi cung cấp các mối quan hệ với hàng tấn vật phẩm ...
tahoe

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

Tôi nghĩ rằng tôi sẽ chơi một ít golf mã với cái này.

FYI: Tôi đang sử dụng automap_base vì chúng tôi có một lược đồ được thiết kế riêng theo yêu cầu kinh doanh. Tôi mới bắt đầu sử dụng SQLAlchemy ngày hôm nay nhưng tài liệu nói rằng automap_base là một phần mở rộng cho kê khai_base dường như là mô hình điển hình trong SQLAlchemy ORM vì vậy tôi tin rằng nó sẽ hoạt động.

Nó không được ưa thích khi theo các khóa ngoại theo giải pháp của Tjorriemorrie , nhưng nó chỉ đơn giản khớp các cột với các giá trị và xử lý các loại Python bằng str () - ing các giá trị cột. Các giá trị của chúng tôi bao gồm các kết quả loại lớp datetime.time và binary.Decimal của Python để nó hoàn thành công việc.

Hy vọng điều này sẽ giúp bất kỳ người qua đường!


1

Tôi biết đây là một bài viết cũ hơn. Tôi đã lấy giải pháp được đưa ra bởi @SashaB và sửa đổi theo nhu cầu của tôi.

Tôi đã thêm những điều sau đây vào nó:

  1. Danh sách bỏ qua trường: Danh sách các trường sẽ bị bỏ qua trong khi tuần tự hóa
  2. Danh sách thay thế trường: Một từ điển chứa tên trường được thay thế bằng các giá trị trong khi tuần tự hóa.
  3. Các phương thức bị xóa và BaseQuery được tuần tự hóa

Mã của tôi là như sau:

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

Hy vọng nó sẽ giúp được ai đó!


1

Sử dụng trình tuần tự hóa tích hợp trong SQLAlchemy:

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

# deserialize object
obj = loads(serialized_obj)

Nếu bạn đang chuyển đối tượng giữa các phiên, hãy nhớ tách đối tượng khỏi phiên hiện tại bằng cách sử dụng session.expunge(obj). Để gắn nó một lần nữa, chỉ cần làm session.add(obj).


Tiện lợi, nhưng không chuyển đổi sang JSON.
blakev

2
Để biết 'tuần tự hóa', hãy xem marshmallow-sqlalchemy . Chắc chắn là giải pháp tốt nhất khi bạn phơi bày đồ vật cho khách hàng. marshmallow-sqlalchemy.readthedocs.io
chribsen

Mô-đun serializer chỉ thích hợp cho các cấu trúc truy vấn. Nó không cần thiết cho: các thể hiện của các lớp do người dùng định nghĩa. Chúng không chứa các tham chiếu đến các công cụ, phiên hoặc cấu trúc biểu thức trong trường hợp điển hình và có thể được nối tiếp trực tiếp.
thomasd

1

đoạn mã sau sẽ tuần tự hóa kết quả sqlalchemy thành 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) 

Gọi vui

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

1

AlchemyEncoder là tuyệt vời nhưng đôi khi thất bại với các giá trị thập phân. Dưới đây là một bộ mã hóa được cải tiến để giải quyết vấn đề thập phân -

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

Khi sử dụng sqlalchemy để kết nối với db I, đây là một giải pháp đơn giản có khả năng cấu hình cao. Sử dụng gấu trúc.

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

Trong Flask, điều này hoạt động và xử lý các trường dữ liệu, chuyển đổi một trường loại
'time': datetime.datetime(2018, 3, 22, 15, 40)thành
"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

Các cuộn cảm serializer tích hợp với utf-8 không thể giải mã byte bắt đầu không hợp lệ cho một số đầu vào. Thay vào đó, tôi đã đi với:

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

Có lẽ bạn có thể sử dụng một lớp học như thế này

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!

Với tất cả các đối tượng có to_dictphương thức


0

Trong khi sử dụng một số đối tượng sql thô và không xác định, sử dụng đã cursor.descriptionxuất hiện để có được thứ tôi đang tìm kiếm:

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
Mã bãi mà không có bất kỳ lời giải thích hiếm khi hữu ích. Stack Overflow là về học tập, không cung cấp đoạn trích để sao chép và dán một cách mù quáng. Vui lòng chỉnh sửa câu hỏi của bạn và giải thích cách nó hoạt động tốt hơn những gì OP cung cấp.
Chris

-2

Từ điển của tôi sử dụng (quá nhiều?):

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

Chạy với bình (bao gồm jsonify) và jar_sqlalchemy để in kết quả đầu ra dưới dạng JSON.

Gọi hàm với jsonify (serialize ()).

Hoạt động với tất cả các truy vấn SQLAlchemy mà tôi đã thử cho đến nay (chạy SQLite3)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.