[英]How to serialize SqlAlchemy result to JSON?
Django 對從 DB 返回的 ORM 模型自動序列化為 JSON 格式。
如何將SQLAlchemy查詢結果序列化為JSON格式?
我試過jsonpickle.encode
但它本身對查詢 object 進行編碼。 我試過json.dumps(items)
但它返回
TypeError: <Product('3', 'some name', 'some desc')> is not JSON serializable
將 SQLAlchemy ORM 對象序列化為 JSON /XML 真的那么難嗎? 它沒有任何默認的序列化程序嗎? 現在序列化 ORM 查詢結果是很常見的任務。
我需要的只是返回 SQLAlchemy 查詢結果的 JSON 或 XML 數據表示。
SQLAlchemy對象查詢結果JSON/XML格式需要在javascript datagird(JQGrid http://www.trirand.com/blog/ )中使用
您可以將對象輸出為字典:
class User:
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
然后你使用User.as_dict()
來序列化你的對象。
你可以使用這樣的東西:
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)
例如,僅擴展名為“parents”的 SQLAlchemy 字段。
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
}
]
}
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
您可以將 RowProxy 轉換為這樣的字典:
d = dict(row.items())
然后將其序列化為 JSON(您必須為諸如datetime
值之類的內容指定一個編碼器)如果您只想要一條記錄(而不是相關記錄的完整層次結構),這並不難。
json.dumps([(dict(row.items())) for row in rs])
我建議使用棉花糖。 它允許您創建序列化程序來表示您的模型實例,並支持關系和嵌套對象。
這是他們文檔中的一個截斷示例。 以 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-sqlalchemy
庫專門集成了 SQLAlchemy 和 marshmallow。 在該庫中,上述Author
模型的架構如下所示:
class AuthorSchema(ModelSchema):
class Meta:
model = Author
集成允許從 SQLAlchemy Column
類型推斷Column
類型。
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 db.Model
類添加了query
和query_class
)。 我已更新編碼器如下:
應用程序/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):
__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
更詳細的解釋。 在您的模型中,添加:
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)
雖然最初的問題可以追溯到一段時間以前,但這里的答案數量(以及我自己的經驗)表明這是一個不平凡的問題,有許多不同的方法,具有不同的復雜性和不同的權衡。
這就是我構建SQLAthanor庫的原因,該庫擴展了 SQLAlchemy 的聲明性 ORM,並具有您可能想要查看的可配置序列化/反序列化支持。
該庫支持:
dict
序列化/反序列化password
值,但從不包括出站password
)您可以在此處查看(我希望!)全面的文檔: https : //sqlathanor.readthedocs.io/en/latest
希望這可以幫助!
自定義序列化和反序列化。
“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
使用 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)
。
這是一個解決方案,可讓您根據需要選擇要包含在輸出中的關系。 注意:這是一個完整的重寫,將 dict/str 作為 arg 而不是列表。 修復一些東西..
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'}}))
它不是那么簡單。 我寫了一些代碼來做到這一點。 我仍在研究它,它使用 MochiKit 框架。 它基本上使用代理和注冊的 JSON 轉換器在 Python 和 Javascript 之間轉換復合對象。
數據庫對象的瀏覽器端是db.js它需要在基本的Python代理源的proxy.js 。
在 Python 端有基本代理模塊。 最后是webserver.py 中的 SqlAlchemy 對象編碼器。 它還取決於在models.py文件中找到的元數據提取器。
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)
def alc2json(row):
return dict([(col, str(getattr(row,col))) for col in row.__table__.columns.keys()])
我想我會用這個玩一些代碼高爾夫。
僅供參考:我正在使用automap_base,因為我們有一個根據業務需求單獨設計的架構。 我今天剛開始使用 SQLAlchemy,但文檔指出 automap_base 是 declarative_base 的擴展,這似乎是 SQLAlchemy ORM 中的典型范例,所以我相信這應該可行。
根據Tjorriemorrie的解決方案,它並不喜歡遵循外鍵,但它只是將列與值匹配並通過 str() 處理列值來處理 Python 類型。 我們的值包括 Python datetime.time 和 decimal.Decimal 類類型結果,因此它可以完成工作。
希望這對任何路過的人有所幫助!
我知道這是一個相當老的帖子。 我采用了@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
希望它可以幫助某人!
在 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)
以下代碼將 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 值而失敗。 這是一個解決十進制問題的改進編碼器 -
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)
當使用 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
雖然是舊帖,可能我沒有回答上面的問題,但我想談談我的連載,至少它對我有用。
我使用 FastAPI、SqlAlchemy 和 MySQL,但我不使用 orm 模型;
# from sqlalchemy import create_engine
# from sqlalchemy.orm import sessionmaker
# engine = create_engine(config.SQLALCHEMY_DATABASE_URL, pool_pre_ping=True)
# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
序列化代碼
import decimal
import datetime
def alchemy_encoder(obj):
"""JSON encoder function for SQLAlchemy special classes."""
if isinstance(obj, datetime.date):
return obj.strftime("%Y-%m-%d %H:%M:%S")
elif isinstance(obj, decimal.Decimal):
return float(obj)
import json
from sqlalchemy import text
# db is SessionLocal() object
app_sql = 'SELECT * FROM app_info ORDER BY app_id LIMIT :page,:page_size'
# The next two are the parameters passed in
page = 1
page_size = 10
# execute sql and return a <class 'sqlalchemy.engine.result.ResultProxy'> object
app_list = db.execute(text(app_sql), {'page': page, 'page_size': page_size})
# serialize
res = json.loads(json.dumps([dict(r) for r in app_list], default=alchemy_encoder))
如果它不起作用,請忽略我的回答。 我參考這里
https://codeandlife.com/2014/12/07/sqlalchemy-results-to-json-the-easy-way/
通過pip install simplejson
並創建一個類
class Serialise(object):
def _asdict(self):
"""
Serialization logic for converting entities using flask's jsonify
:return: An ordered dictionary
:rtype: :class:`collections.OrderedDict`
"""
result = OrderedDict()
# Get the columns
for key in self.__mapper__.c.keys():
if isinstance(getattr(self, key), datetime):
result["x"] = getattr(self, key).timestamp() * 1000
result["timestamp"] = result["x"]
else:
result[key] = getattr(self, key)
return result
並將這個類繼承到每個 orm 類,以便這個_asdict
函數注冊到每個 ORM 類和繁榮。 並在任何地方使用 jsonify
(對Sasha B非常出色的答案的微小調整)
這專門將 datetime 對象轉換為字符串,在原始答案中將轉換為None
:
# Standard library imports
from datetime import datetime
import json
# 3rd party imports
from sqlalchemy.ext.declarative import DeclarativeMeta
class JsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
dict = {}
# Remove invalid fields and just get the column attributes
columns = [x for x in dir(obj) if not x.startswith("_") and x != "metadata"]
for column in columns:
value = obj.__getattribute__(column)
try:
json.dumps(value)
dict[column] = value
except TypeError:
if isinstance(value, datetime):
dict[column] = value.__str__()
else:
dict[column] = None
return dict
return json.JSONEncoder.default(self, obj)
class SqlToDict:
def __init__(self, data) -> None:
self.data = data
def to_timestamp(self, date):
if isinstance(date, datetime):
return int(datetime.timestamp(date))
else:
return date
def to_dict(self) -> List:
arr = []
for i in self.data:
keys = [*i.keys()]
values = [*i]
values = [self.to_timestamp(d) for d in values]
arr.append(dict(zip(keys, values)))
return arr
例如:
SqlToDict(data).to_dict()
帶有 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 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
方法
在使用一些原始 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)
這是一個JSONEncoder
版本,它保留模型列順序並且只保留遞歸定義的列和關系字段。 它還格式化大多數 JSON 不可序列化類型:
import json
from datetime import datetime
from decimal import Decimal
import arrow
from sqlalchemy.ext.declarative import DeclarativeMeta
class SQLAlchemyJSONEncoder(json.JSONEncoder):
"""
SQLAlchemy ORM JSON Encoder
If you have a "backref" relationship defined in your SQLAlchemy model,
this encoder raises a ValueError to stop an infinite loop.
"""
def default(self, obj):
if isinstance(obj, datetime):
return arrow.get(obj).isoformat()
elif isinstance(obj, Decimal):
return float(obj)
elif isinstance(obj, set):
return sorted(obj)
elif isinstance(obj.__class__, DeclarativeMeta):
for attribute, relationship in obj.__mapper__.relationships.items():
if isinstance(relationship.__getattribute__("backref"), tuple):
raise ValueError(
f'{obj.__class__} object has a "backref" relationship '
"that would cause an infinite loop!"
)
dictionary = {}
column_names = [column.name for column in obj.__table__.columns]
for key in column_names:
value = obj.__getattribute__(key)
if isinstance(value, datetime):
value = arrow.get(value).isoformat()
elif isinstance(value, Decimal):
value = float(value)
elif isinstance(value, set):
value = sorted(value)
dictionary[key] = value
for key in [
attribute
for attribute in dir(obj)
if not attribute.startswith("_")
and attribute != "metadata"
and attribute not in column_names
]:
value = obj.__getattribute__(key)
dictionary[key] = value
return dictionary
return super().default(obj)
我成功地使用了這個包: https : //github.com/n0nSmoker/SQLAlchemy-serializer
您可以在模型上執行此操作:
from sqlalchemy_serializer import SerializerMixin
class SomeModel(db.Model, SerializerMixin):
...
它添加了完全遞歸的 to_dict:
item = SomeModel.query.filter(...).one()
result = item.to_dict()
它可以讓你制定規則來避免無限遞歸:
result = item.to_dict(rules=('-somefield', '-some_relation.nested_one.another_nested_one'))
如果您正在使用 Flask 並且只想快速查詢:
def get_cats():
sql = text("select * from cat")
sql_params = {}
result = db.session.execute(sql, sql_params)
row_list = result.fetchall()
data = [dict(r) for r in row_list]
response = jsonify({
'data': [{
'categorias': data
}]
})
return response
https://flask-restplus.readthedocs.io/en/stable/marshalling.html
from flask_restplus import fields, Namespace, marshal
api = Namespace("Student data")
db_data = Student_details.query.all()
data_marshal_obj = api.model(" Data", {
"id": fields.String(),
"number": fields.Integer(),
"house_name": fields.String(),
})
data_in_json_serialize = marshal(db_data, data_marshal_obj)}
print(type(data_in_json_serialize )) # <class 'dict'>
經過一番嘗試,我想出了自己的解決方案,如下所示
def to_dict(self):
keys = self.__mapper__.attrs.keys()
attrs = vars(self)
return { k : attrs[k] for k in keys}
將as_dict
方法添加到任何 model 的動態方法
from sqlalchemy.inspection import inspect
def implement_as_dict(model):
if not hasattr(model,"as_dict"):
column_names=[]
imodel = inspect(model)
for c in imodel.columns:
column_names.append(c.key)
#define model.as_dict()
def as_dict(self):
d = {}
for c in column_names:
d[c] = getattr(self,c)
return d
setattr(model,"as_dict",as_dict)
#model definition
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
# adding as_dict definition to model
implement_as_dict(User)
那么你可以使用
user = session.query(User).filter_by(name='rick').first()
user.as_dict()
#sample output
{"id":1,"name":"rick"}
我的實現
def obj_to_dict(obj, remove=['_sa_instance_state'], debug=False):
result = {}
if type(obj).__name__ == "Row":
return dict(obj)
obj = obj.__dict__
for key in obj:
if key in remove:
continue
result[key] = obj[key]
if debug:
print(result)
return result
我使用(太多?)字典:
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
使用flask(包括jsonify)和flask_sqlalchemy 運行以將輸出打印為JSON。
使用 jsonify(serialize()) 調用函數。
適用於我迄今為止嘗試過的所有 SQLAlchemy 查詢(運行 SQLite3)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.