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/ )で使用する必要があります
次のようなものを使用できます。
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)
これにより、すべての子、すべての子、およびすべての子がエンコードされます。基本的に、データベース全体をエンコードする可能性があります。以前にエンコードされたものに到達すると、「なし」としてエンコードされます。
別の選択肢としては、おそらくもっと良い方法として、展開したいフィールドを指定できるようにすることです:
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フィールドのみを展開します。
オブジェクトを辞書として出力することができます:
class User:
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
そして、User.as_dict()
を使用してオブジェクトをシリアル化します。
RowProxyを次のような辞書に変換できます。
d = dict(row.items())
次に、それをJSONにシリアル化します(datetime
値などのエンコーダーを指定する必要があります)1つのレコード(関連するレコードの完全な階層ではない)だけが必要な場合もそれほど難しくありません。
json.dumps([(dict(row.items())) for row in rs])
最近の表面化ライブラリ Marshmallow を使用することをお勧めします。リレーションとネストされたオブジェクトをサポートするモデルインスタンスを表すシリアライザーを作成できます。
Theier SQLAlchemy Example をご覧ください。
Flask-JsonTools パッケージには JsonSerializableBase モデルの基本クラスが実装されています。
使用法:
from sqlalchemy.ext.declarative import declarative_base
from flask.ext.jsontools import JsonSerializableBase
Base = declarative_base(cls=(JsonSerializableBase,))
class User(Base):
#...
これで、User
モデルは魔法のようにシリアライズ可能になりました。
フレームワークがFlaskでない場合は、 コードを取得する
セキュリティ上の理由から、モデルのすべてのフィールドを返さないでください。私はそれらを選択することを好みます。
Flaskのjsonエンコーディングは、UUID、日時、および関係をサポートするようになりました(さらに、flask_sqlalchemy query_class
クラスのquery
および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を追加し、結果リストを返すと、出力は次のようになります。
[
{
"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"
}
]
これとしてSqlAlchemyのイントロスペクションを使用できます。
mysql = SQLAlchemy()
from sqlalchemy import inspect
class Contacts(mysql.Model):
__table= '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に変換
より詳細な説明。モデルに次を追加します。
def as_dict(self):
return {c.name: str(getattr(self, c.name)) for c in self.__table__.columns}
str()
はpython 3用です。したがって、python 2を使用する場合はunicode()
を使用します。日付の逆シリアル化に役立ちます。それらを扱っていない場合は削除できます。
これで、このようにデータベースをクエリできます
some_result = User.query.filter_by(id=current_user.id).first().as_dict()
First()
は、奇妙なエラーを避けるために必要です。 as_dict()
は結果をデシリアライズします。デシリアライズ後、jsonに移行する準備ができました
jsonify(some_result)
それはそれほど単純ではありません。これを行うためのコードをいくつか書きました。私はまだそれに取り組んでおり、MochiKitフレームワークを使用しています。基本的に、プロキシと登録済みJSONコンバーターを使用して、PythonとJavascriptの間の複合オブジェクトを変換します。
データベースオブジェクトのブラウザ側は db.js です- proxy.js の基本的なPythonプロキシソースが必要です。
Python側には、ベース プロキシモジュール があります。そして最後に webserver.py のSqlAlchemyオブジェクトエンコーダー。また、 models.py ファイルにあるメタデータエクストラクターにも依存します。
以下に、出力に含めたいリレーションを好きなだけ選択できるソリューションを示します。注:これは、リストではなく引数として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'}}))
元の質問はしばらく前に遡りますが、ここでの回答の数(および私自身の経験)は、それがさまざまなトレードオフでさまざまな複雑さのさまざまなアプローチを備えた非自明な質問であることを示唆しています。
そのため、SQLAlchemyの宣言型ORMを拡張する SQLAthanor ライブラリを構築しました。これは、構成可能なシリアル化/逆シリアル化のサポートを備えています。
ライブラリは以下をサポートします:
dict
との間のシリアル化/逆シリアル化password
値をサポートしたいが、決して含めないanoutboundone)こちらの包括的なドキュメントをチェックできます: https://sqlathanor.readthedocs.io/en/latest
お役に立てれば!
カスタムシリアル化と逆シリアル化。
"from_json"(クラスメソッド)は、jsonデータに基づいてModelオブジェクトを構築します。
"deserialize"はインスタンスでのみ呼び出すことができ、jsonからのすべてのデータをModelインスタンスにマージします。
"serialize"-再帰的シリアル化
_ 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
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)
を実行します。
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クラスタイプの結果で構成されているため、仕事が完了します。
これが通行人を助けることを願っています!
次のコードは、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)
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)
Flaskでは、これは動作し、データ時間フィールドを処理して、タイプのフィールドを変換します'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)
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
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 __== "__main__":
users = User(email="[email protected]"), User(email="[email protected]")
db.create_all()
db.session.add_all(users)
db.session.commit()
app.run()
/users/
ルートはユーザーのリストを返すようになりました。
[
{"email": "[email protected]", "id": 1},
{"email": "[email protected]", "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":"[email protected]",
"id":1
},
{
"email":"[email protected]",
"id":2
}
]
}
from flask.json import JSONEncoder
class CustomJSONEncoder(JSONEncoder):
"Add support for serializing timedeltas"
def default(o):
if type(o) == datetime.timedelta:
return str(o)
else:
return super().default(o)
app.json_encoder = CustomJSONEncoder
これはかなり古い記事です。 @SashaBから提供されたソリューションを使用し、必要に応じて変更しました。
次のことを追加しました。
私のコードは次のとおりです。
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
それが誰かを助けることを願っています!