SqlAlchemyの結果をJSONにシリアル化する方法は?


189

Djangoには、DBからJSON形式に返されるORMモデルの優れた自動シリアル化機能があります。

SQLAlchemyクエリの結果をJSON形式にシリアル化する方法

私は試しましたjsonpickle.encodeが、それはクエリオブジェクト自体をエンコードします。試しましたjson.dumps(items)が、戻ってきました

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

SQLAlchemy ORMオブジェクトをJSON / XMLにシリアル化するのは本当に難しいですか?デフォルトのシリアライザはありませんか?今日、ORMクエリの結果をシリアル化することは非常に一般的なタスクです。

SQLAlchemyクエリ結果のJSONまたはXMLデータ表現を返すだけです。

JSON / XML形式のSQLAlchemyオブジェクトのクエリ結果は、javascript datagird(JQGrid http://www.trirand.com/blog/)で使用する必要があります


これは私にとって有効な回避策です。ここにリンクの説明を入力してください
octaedro

回答:


127

フラットな実装

あなたはこのようなものを使うことができます:

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)

次に、以下を使用してJSONに変換します。

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

エンコードできないフィールドは無視します(「なし」に設定します)。

これは関係を自動拡張しません(これは自己参照につながり、永久にループする可能性があるため)。

再帰的で非循環的な実装

ただし、永久にループしたい場合は、次のように使用できます。

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

次に、以下を使用してオブジェクトをエンコードします。

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

これは、すべての子供とそのすべての子供、およびすべての子供をエンコードします...データベース全体を基本的にエンコードする可能性があります。以前にエンコードされたものに到達すると、「なし」としてエンコードされます。

再帰的、おそらく循環的、選択的な実装

おそらくより良いもう1つの方法は、展開するフィールドを指定できるようにすることです。

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

これで次のように呼び出すことができます:

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

たとえば、「親」と呼ばれるSQLAlchemyフィールドのみを展開します。


それは素晴らしい応答ですが、「フラットでないメソッドとの関係にぶつかると、いつでも「BaseQueryをエンコードできませんでした」と思いますか?
Ben Kilah

1
@SashaB関係が繰り返される場合をさらに細かくターゲット設定してみませんか たとえば、私がonline_orderとを持っている場合address、両方にとの関係useronline_orderありますが、との関係もありaddressます。これらすべてをシリアル化したい場合はaddress、に含める必要がありますが、およびの両方との関係のため、fields_to_expand冗長にシリアルaddress化したくありません。useronline_order
Chrispy、2015年

2
@BenKilahご想像のとおり、Flask-SqlAlchemyを使用しており、モデルはBaseではなくdb.Modelから継承しています。その場合は、となるように変更for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:してくださいfor field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and not x.startswith('query')]:。このソリューションでは、「クエリ」という名前のプロパティ/関係を持つことができないことに
注意してください

私と同じ方法ですが、はるかに複雑です。stackoverflow.com/questions/7102754/...
TYAN


270

オブジェクトを辞書として出力するだけです:

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

次にUser.as_dict()、オブジェクトのシリアル化に使用します。

sqlalchemy行オブジェクトをpython dict変換するで説明されています


2
@ charlax、DateTimeをどのように修正しますか?これを使用すると、json.dumpsを実行すると、「datetime.datetime(2013、3、22、16、50、11)はJSONシリアル化できません」というメッセージが表示されます
Asken

1
それはJSONEncoderオブジェクトの責任です。これをサブクラス化して、datetimeを含むいくつかのオブジェクトの独自のエンコーダーを定義できます。Flaskたとえば、(最新バージョンの)そのままのJSONでエンコード日時をサポートすることに注意してください。
charlax 2013年

3
sqlalchemyの「宣言」メソッドを使用する場合は、このようなものをカスタムBaseクラスに追加できます。これは、所有しているORMオブジェクトでmy_orm_object.toDict()を呼び出すことができるため、非常に便利です。同様に、日付やブロブなどを処理するためにtoDictメソッドとカスタムエンコーダーを使用する.toJSON()メソッドを定義できます
FredL

7
日時もサポートするには:return {c.name: unicode(getattr(self, c.name)) for c in self.__table__.columns}
ショーハム

1
クラス変数が列名と同じでない場合、これは機能しません。代わりにクラス名を取得する方法はありますか?
ジェームズバーク

55

RowProxyを次のようにdictに変換できます。

 d = dict(row.items())

次に、それをJSONにシリアル化します(datetime値などのエンコーダーを指定する必要があります)1つのレコードだけが必要な場合(関連するレコードの完全な階層ではなく)も、それほど難しくありません。

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

1
これは、db.engine.connect()をconとして使用した私のカスタムSQLクエリで機能します:rs = con.execute(sql)
JZ。

1
これははるかにシンプルで機能します。この回答と承認された回答の違いは何ですか?
Sundeep、

46

マシュマロの使用をお勧めします。リレーションとネストされたオブジェクトをサポートして、モデルインスタンスを表すシリアライザーを作成できます。

ここに彼らのドキュメントから切り捨てられた例があります。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))

そのクラスのマシュマロスキーマは次のように構成されます。

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)

...そしてこのように使用されます:

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

...このような出力を生成します:

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

完全なFlask-SQLAlchemyの例をご覧ください

と呼ばれるライブラリは、marshmallow-sqlalchemySQLAlchemyとマシュマロを具体的に統合します。そのライブラリでは、Author上記のモデルのスキーマは次のようになります。

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

統合により、フィールドタイプをSQLAlchemy Columnタイプから推測できます。

マシュマロsqlalchemyここ。


12
また、スキーマ生成を簡素化するmarshmallow-sqlalchemy.readthedocs.io/en/latestも見つかりました
Foo L

40

Pythonの3.7+、フラスコ1.1+は、内蔵で使用できるデータクラスのパッケージを

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

/users/ルートは現在のユーザーのリストを返します。

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

関連モデルを自動シリアル化

@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

からの返答jsonify(account)はこれだろう。

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

デフォルトのJSONエンコーダーを上書きする

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
これは単純なもののように見えます。逆シリアル化にも機能しますか?
Ender2050

あなたは、キーワード引数開梱を使用してモデルに解析JSONの辞書を変換することができます:data = request.json['user']; user = User(**data)
トム・

3
これid: int = Columnは機能しますが機能しid = Columnませんが、jsonがフィールドをシリアル化するための静的型付けを宣言する必要があるように見え{}ます。そうでない場合、空のオブジェクトが取得されます。
Ambroise Rabier、

1
これは私にとってうまくいきました、なぜこれが受け入れられた答えではないのですか?これをFlask-Marshmallowで動作させるために、私はapp_contextを何時間も遊んでいます。
Nick Dat Le

1
私のためにも働いた。Python 3.6を使用している場合は、パッケージをインストールするだけですpipenv install dataclasses。そして、それはうまくいきます。
AleksandrH

14

Flask-JsonToolsパッケージにはJsonSerializableBaseの実装がありますモデル用 Baseクラスのます。

使用法:

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

Base = declarative_base(cls=(JsonSerializableBase,))

class User(Base):
    #...

これで、Userモデルは魔法のようにシリアライズ可能になります。

フレームワークがFlaskでない場合は、コードを取得することができます


2
これは、単一の行のみをシリアル化するため、問題の半分しか解決しません。クエリ結果全体をシリアル化する方法は?
Steve Bennett

@SteveBennettはjsontoolsのjsonapiを使用して応答をエンコードします。これにより、戻りオブジェクトが自動的にエンコードされます
Tjorriemorrie、2015

非常に単純なsqlalchemyモデルがあり、TypeError:<ORM.State object at 0x03577A50> is not JSON serializable
Matej

1
最終的には、私のモデルオブジェクトで__json __()を明示的に呼び出すことで機能しました:return my_object .__ json __()
Matej

このライブラリは、Flask 1.0ではimport flask.ext.whateverサポートされなくなったため、Flask 1.0以降では機能しません。
Adarsh Madrecha

14

セキュリティ上の理由から、モデルのすべてのフィールドを返すことはできません。私はそれらを選択的に選ぶことを好みます。

Flaskのjsonエンコーディングは、UUID、日時、および関係をサポートするようになりました(追加されqueryquery_classflask_sqlalchemy db.Modelクラス用)。エンコーダーを次のように更新しました。

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

これで__json__、エンコードしたいフィールドのリストを返すプロパティをオプションで追加できます:

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

ビューに@jsonapiを追加し、resultlistを返すと、出力は次のようになります。

[

{

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

]

綺麗な!繰り返しますが、愚かな小さなタスクごとにファットパッケージが必要ない場合があることを証明します。DSLを学ぶのは、「難しい」方法を行うよりも難しい場合があることを証明します。ここに着く前に、多くのJSONおよびRESTパッケージを調べました。確かに、これにはパッケージ(flask_jsontools)がviews.pyなどに追加する@jsonapiため@app.routeに)必要ですが、その単純さは気に入っています。私はそれが安い日付だと思いますが、日付ではなく日付を追加したので、json_encoder.pyに自分で追加しました:^ ^ ^ ^value=...if isinstance(value, date):data[field] = datetime.combine(value, time.min).isoformat()else:try:...
juanitogan

10

SqlAlchemyのイントロスペクションを次のように使用できます。

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

ここで答えからインスピレーションを得てください: sqlalchemy行オブジェクトをpython dictに変換します


5

より詳細な説明。モデルに、以下を追加します。

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

str()python 2の使用を使用している場合ので、pythonの3のためですunicode()。日付の逆シリアル化に役立つはずです。それらを扱わない場合は削除できます。

これで、このようにデータベースをクエリできます

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

First()奇妙なエラーを避けるために必要です。as_dict()結果を逆シリアル化します。逆シリアル化後、jsonに変換する準備ができました

jsonify(some_result)

3

それはそれほど簡単ではありません。これを行うためのコードをいくつか書きました。私はまだそれに取り組んでおり、MochiKitフレームワークを使用しています。基本的に、プロキシと登録されたJSONコンバーターを使用して、PythonとJavaScriptの間で複合オブジェクトを変換します。

データベースオブジェクトのブラウザー側はdb.jsです。proxy.js に基本的なPythonプロキシソースが必要です。です。

Python側には、ベースプロキシモジュールがあります。次に、最後にwebserver.pyの SqlAlchemyオブジェクトエンコーダー。また、models.pyファイルにあるメタデータエクストラクターにも依存します。


一見するとかなり複雑です...私が必要なのは、SQLAlchemyオブジェクトのクエリ結果をJSON / XML形式で取得して、それをjavascript datagird(JQGrid trirand.com/blog)で使用することです
Zelid

場合によっては、一見したところ問題が複雑になることがあります...これは、外部キーとして返されたオブジェクトを処理し、深くネストされた関係で発生する無限再帰を回避しようとします。ただし、基本タイプのみを返すカスタムクエリをいくつか作成し、simplejsonで直接それらをシリアル化することもできます。
キース

1
そう、多分私は本当にSQLAlchemyを使って辞書のクエリを行い、保存/更新アクションのみを実行するORMの利点を利用するでしょう。
Zelid

3

元の質問はしばらく前に戻りますが、ここでの回答の数(および私自身の経験)は、さまざまなトレードオフでさまざまな複雑さのさまざまなアプローチを伴う重要な質問であることを示唆しています。

だからこそ、SQLAlchemyの宣言型ORMを構成するシリアライゼーション/デシリアライゼーションのサポートを拡張するSQLAthanorライブラリを作成しました。

ライブラリは以下をサポートします:

  • Python 2.7、3.4、3.5、および3.6。
  • SQLAlchemyバージョン0.9以降
  • JSON、CSV、YAML、Pythonとのシリアライズ/デシリアライズ dict
  • 列/属性、関係、ハイブリッドプロパティ、および関連付けプロキシのシリアル化/逆シリアル化
  • 有効にすると、特定のフォーマットおよび列/関係/属性のシリアライズの無効化(例えば、あなたがサポートするインバウンド password値を、しかし含まれませんアウトバウンド 1)を
  • シリアル化前およびシリアル化解除後の値の処理(検証または型強制のため)
  • Pythonicであり、SQLAlchemy独自のアプローチとシームレスに一貫している、かなり単純な構文

(私は願っています!)包括的なドキュメントをここでチェックできます: https //sqlathanor.readthedocs.io/en/latest

お役に立てれば!


2

カスタムのシリアライズとデシリアライズ。

"from_json"(クラスメソッド)は、jsonデータに基づいてModelオブジェクトを構築します。

「デシリアライズ」はインスタンスでのみ呼び出すことができ、jsonからのすべてのデータをモデルインスタンスにマージできます。

「シリアライズ」 -再帰的なシリアライズ

__write_only__プロパティは、書き込み専用プロパティ(たとえば、「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

これは、出力に含めたい関係を必要なだけ深く選択できるソリューションです。注:これはリストではなくdict / strを引数として使用する完全な書き直しです。いくつかのものを修正します。

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

したがって、person / family / homes / roomsを使用する例については、それをjsonに変換するだけです。

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

これは問題ありません。すべてのオブジェクトが持つように、基本クラスに配置するだけです。jsonエンコーディングはあなたにお任せします...
tahoe

(注)このバージョンでは...すべてのリストの関係は非常にアイテムのトンとの関係を提供慎重に取得すること
タホ

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

これでちょっとコードゴルフをしようと思った。

参考automap_baseを使用していますビジネス要件に応じて個別に設計されたスキーマがあるため、私はています。今日、SQLAlchemyを使い始めたばかりですが、automap_baseはSQLAlchemy ORMの典型的なパラダイムと思われるdeclarative_baseの拡張であるとドキュメントに記載されているので、これでうまくいくと思います。

Tjorriemorrieのソリューションに従って次の外部キーを使用して空想を取得するのではなく、単に列を値に一致させ、列の値をstr()-ingすることによってPython型を処理します。私たちの値は、Pythonのdatetime.timeとdecimal.Decimalのクラス型の結果で構成されているので、仕事が完了します。

これがどんな通行人にも役立つことを願っています!


1

私はこれがかなり古い投稿であることを知っています。私は@SashaBによって与えられた解決策を取り、私の必要に応じて修正しました。

私はそれに以下のものを追加しました:

  1. フィールド無視リスト:シリアライズ中に無視されるフィールドのリスト
  2. フィールド置換リスト:シリアライズ中に値で置換されるフィールド名を含む辞書。
  3. 削除されたメソッドとBaseQueryのシリアル化

私のコードは次のとおりです:

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

それが誰かを助けることを願っています!


1

SQLAlchemyの組み込みシリアライザーを使用します。

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

# deserialize object
obj = loads(serialized_obj)

セッション間でオブジェクトを転送する場合は、を使用して、オブジェクトを現在のセッションから切り離してくださいsession.expunge(obj)。再度取り付けるには、を実行してくださいsession.add(obj)


気の利いた、しかしJSONには変換されません。
blakev 2016

2
JSONの「シリアル化」については、marshmallow-sqlalchemyを確認してください。オブジェクトをクライアントに公開する場合は、間違いなく最良のソリューションです。marshmallow-sqlalchemy.readthedocs.io
2016

シリアライザモジュールはクエリ構造にのみ適しています。ユーザー定義クラスのインスタンスには必要ありません。これらには、通常の場合、エンジン、セッション、または式の構成への参照は含まれておらず、直接シリアル化できます。
トーマス

1

次のコードは、sqlalchemyの結果を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) 

楽しみを呼んで、

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

1

AlchemyEncoderは素晴らしいですが、Decimal値で失敗することがあります。これは、10進数の問題を解決する改良されたエンコーダです-

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

sqlalchemyを使用してデータベースに接続する場合、これは高度に設定可能なシンプルなソリューションです。パンダを使用してください。

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

フラスコの下に、この作品とハンドルは種類のフィールドを変換し、フィールドをdatatime
'time': datetime.datetime(2018, 3, 22, 15, 40)
"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

組み込みのシリアライザーチョークとutf-8は、一部の入力の無効な開始バイトをデコードできません。代わりに、私は一緒に行きました:

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

多分あなたはこのようなクラスを使うことができます

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!

これですべてのオブジェクトにto_dictメソッドがあります


0

いくつかの生のsqlと未定義のオブジェクトを使用cursor.descriptionしているときに、を使用すると、私が探していたものを取得するように見えました:

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
説明のないコードダンプが役立つことはほとんどありません。Stack Overflowは学習に関するものであり、盲目的にコピーして貼り付けるスニペットを提供するものではありません。質問を編集して、OPが提供するものよりもどのように機能するかを説明してください。
クリス

-2

(多すぎる?)辞書を利用する私の見解:

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

フラスコ(jsonifyを含む)およびフラスコ_sqlalchemyで実行して、出力をJSONとして出力します。

jsonify(serialize())で関数を呼び出します。

これまでに試したすべてのSQLAlchemyクエリに対応(SQLite3を実行)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.