[英]TypeError: Object of type ObjectId is not JSON serializable
[英]TypeError: ObjectId('') is not JSON serializable
在使用 Python 查詢文檔上的聚合 function 后,我從 MongoDB 回復,它返回有效響應,我可以打印它但不能返回它。
錯誤:
TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable
打印:
{'result': [{'_id': ObjectId('51948e86c25f4b1d1c0d303c'), 'api_calls_with_key': 4, 'api_calls_per_day': 0.375, 'api_calls_total': 6, 'api_calls_without_key': 2}], 'ok': 1.0}
但是當我嘗試返回時:
TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable
這是 RESTfull 調用:
@appv1.route('/v1/analytics')
def get_api_analytics():
# get handle to collections in MongoDB
statistics = sldb.statistics
objectid = ObjectId("51948e86c25f4b1d1c0d303c")
analytics = statistics.aggregate([
{'$match': {'owner': objectid}},
{'$project': {'owner': "$owner",
'api_calls_with_key': {'$cond': [{'$eq': ["$apikey", None]}, 0, 1]},
'api_calls_without_key': {'$cond': [{'$ne': ["$apikey", None]}, 0, 1]}
}},
{'$group': {'_id': "$owner",
'api_calls_with_key': {'$sum': "$api_calls_with_key"},
'api_calls_without_key': {'$sum': "$api_calls_without_key"}
}},
{'$project': {'api_calls_with_key': "$api_calls_with_key",
'api_calls_without_key': "$api_calls_without_key",
'api_calls_total': {'$add': ["$api_calls_with_key", "$api_calls_without_key"]},
'api_calls_per_day': {'$divide': [{'$add': ["$api_calls_with_key", "$api_calls_without_key"]}, {'$dayOfMonth': datetime.now()}]},
}}
])
print(analytics)
return analytics
db 連接良好,收集也在那里,我得到了有效的預期結果,但是當我嘗試返回時,它給了我 Json 錯誤。 知道如何將響應轉換回 JSON。 謝謝
您應該定義自己的JSONEncoder
並使用它:
import json
from bson import ObjectId
class JSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
return json.JSONEncoder.default(self, o)
JSONEncoder().encode(analytics)
也可以通過以下方式使用它。
json.encode(analytics, cls=JSONEncoder)
>>> from bson import Binary, Code
>>> from bson.json_util import dumps
>>> dumps([{'foo': [1, 2]},
... {'bar': {'hello': 'world'}},
... {'code': Code("function x() { return 1; }")},
... {'bin': Binary("")}])
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]'
來自json_util 的實際示例。
與 Flask 的 jsonify 不同,"dumps" 將返回一個字符串,因此它不能用作 Flask 的 jsonify 的 1:1 替換。
但是這個問題表明我們可以使用 json_util.dumps() 進行序列化,使用 json.loads() 轉換回 dict 並最終在其上調用 Flask 的 jsonify。
示例(源自上一個問題的答案):
from bson import json_util, ObjectId
import json
#Lets create some dummy document to prove it will work
page = {'foo': ObjectId(), 'bar': [ObjectId(), ObjectId()]}
#Dump loaded BSON to valid JSON string and reload it as dict
page_sanitized = json.loads(json_util.dumps(page))
return page_sanitized
此解決方案會將 ObjectId 和其他(即二進制、代碼等)轉換為等效的字符串,例如“$oid”。
JSON 輸出如下所示:
{
"_id": {
"$oid": "abc123"
}
}
大多數收到“not JSON serializable”錯誤的用戶只需要在使用json.dumps
時指定default=str
。 例如:
json.dumps(my_obj, default=str)
這將強制轉換為str
,從而防止錯誤。 當然,然后查看生成的輸出以確認它是您需要的。
from bson import json_util
import json
@app.route('/')
def index():
for _ in "collection_name".find():
return json.dumps(i, indent=4, default=json_util.default)
這是將 BSON 轉換為 JSON 對象的示例示例。 你可以試試這個。
作為快速替換,您可以將{'owner': objectid}
更改為{'owner': str(objectid)}
。
但是定義您自己的JSONEncoder
是一個更好的解決方案,這取決於您的要求。
在這里發布,因為我認為它可能對使用Flask
和pymongo
人有用。 這是我目前允許燒瓶編組 pymongo bson 數據類型的“最佳實踐”設置。
mongoflask.py
from datetime import datetime, date
import isodate as iso
from bson import ObjectId
from flask.json import JSONEncoder
from werkzeug.routing import BaseConverter
class MongoJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, (datetime, date)):
return iso.datetime_isoformat(o)
if isinstance(o, ObjectId):
return str(o)
else:
return super().default(o)
class ObjectIdConverter(BaseConverter):
def to_python(self, value):
return ObjectId(value)
def to_url(self, value):
return str(value)
應用程序
from .mongoflask import MongoJSONEncoder, ObjectIdConverter
def create_app():
app = Flask(__name__)
app.json_encoder = MongoJSONEncoder
app.url_map.converters['objectid'] = ObjectIdConverter
# Client sends their string, we interpret it as an ObjectId
@app.route('/users/<objectid:user_id>')
def show_user(user_id):
# setup not shown, pretend this gets us a pymongo db object
db = get_db()
# user_id is a bson.ObjectId ready to use with pymongo!
result = db.users.find_one({'_id': user_id})
# And jsonify returns normal looking json!
# {"_id": "5b6b6959828619572d48a9da",
# "name": "Will",
# "birthday": "1990-03-17T00:00:00Z"}
return jsonify(result)
return app
為什么要這樣做而不是為 BSON 或mongod 擴展 JSON提供服務?
我認為為 mongo 提供特殊的 JSON 會給客戶端應用程序帶來負擔。 大多數客戶端應用程序不會關心以任何復雜的方式使用 mongo 對象。 如果我提供擴展的 json,現在我必須在服務器端和客戶端使用它。 ObjectId
和Timestamp
作為字符串更容易使用,這使所有這些 mongo 編組瘋狂隔離到服務器。
{
"_id": "5b6b6959828619572d48a9da",
"created_at": "2018-08-08T22:06:17Z"
}
我認為對於大多數應用程序來說,這比使用它更容易。
{
"_id": {"$oid": "5b6b6959828619572d48a9da"},
"created_at": {"$date": 1533837843000}
}
這就是我最近修復錯誤的方式
@app.route('/')
def home():
docs = []
for doc in db.person.find():
doc.pop('_id')
docs.append(doc)
return jsonify(docs)
就我而言,我需要這樣的東西:
class JsonEncoder():
def encode(self, o):
if '_id' in o:
o['_id'] = str(o['_id'])
return o
我知道我發布晚了,但我認為這至少會幫助一些人!
tim 和 defuz 提到的兩個例子(投票最高)都運行良好。 但是,有時可能存在顯着的微小差異。
Pymongo 提供了 json_util - 你可以使用它來處理 BSON 類型
輸出:{ "_id": { "$oid": "abc123" } }
輸出:{“_id”:“abc123”}
盡管第一種方法看起來很簡單,但這兩種方法都需要很少的努力。
如果要將其作為 JSON 響應發送,則需要分兩步進行格式化
json_util.dumps()
來隱藏 BSON 中的ObjectId
響應 JSON 兼容格式,即"_id": {"$oid": "123456789"}
上述從json_util.dumps()
獲得的 JSON 響應將帶有反斜杠和引號
json
json.loads()
from bson import json_util
import json
bson_data = [{'_id': ObjectId('123456789'), 'field': 'somedata'},{'_id': ObjectId('123456781'), 'field': 'someMoredata'}]
json_data_with_backslashes = json_util.dumps(bson_data)
# output will look like this
# "[{\"_id\": {\"$oid\": \"123456789\"}, \"field\": \"somedata\"},{\"_id\": {\"$oid\": \"123456781\"}, \"field\": \"someMoredata\"}]"
json_data = json.loads(json_data_with_backslashes)
# output will look like this
# [{"_id": {"$oid": "123456789"},"field": "somedata"},{"_id": {"$oid": "123456781"},"field": "someMoredata"}]
Flask 的 jsonify 提供了安全性增強,如JSON Security 中所述。 如果自定義編碼器與 Flask 一起使用,最好考慮JSON 安全性中討論的要點
我想提供一個額外的解決方案來改進已接受的答案。 我以前在這里的另一個線程中提供了答案。
from flask import Flask
from flask.json import JSONEncoder
from bson import json_util
from . import resources
# define a custom encoder point to the json_util provided by pymongo (or its dependency bson)
class CustomJSONEncoder(JSONEncoder):
def default(self, obj): return json_util.default(obj)
application = Flask(__name__)
application.json_encoder = CustomJSONEncoder
if __name__ == "__main__":
application.run()
對於那些需要使用 Flask 通過 Jsonify 返回數據的人:
cursor = db.collection.find()
data = []
for doc in cursor:
doc['_id'] = str(doc['_id']) # This does the trick!
data.append(doc)
return jsonify(data)
如果您不需要記錄的 _id,我建議您在查詢數據庫時取消設置它,這將使您能夠直接打印返回的記錄,例如
要在查詢時取消設置 _id 然后在循環中打印數據,您可以編寫如下內容
records = mycollection.find(query, {'_id': 0}) #second argument {'_id':0} unsets the id from the query
for record in records:
print(record)
如果你不想要_id
響應,你可以像這樣重構你的代碼:
jsonResponse = getResponse(mock_data)
del jsonResponse['_id'] # removes '_id' from the final response
return jsonResponse
這將刪除TypeError: ObjectId('') is not JSON serializable
錯誤。
你可以試試:
objectid = str(ObjectId("51948e86c25f4b1d1c0d303c"))
解決方案:mongoengine + 棉花糖
如果您使用mongoengine
和marshamallow
那么此解決方案可能適用於您。
基本上,我從棉花糖導入了String
字段,並將默認Schema id
覆蓋為String
編碼。
from marshmallow import Schema
from marshmallow.fields import String
class FrontendUserSchema(Schema):
id = String()
class Meta:
fields = ("id", "email")
from bson.objectid import ObjectId
from core.services.db_connection import DbConnectionService
class DbExecutionService:
def __init__(self):
self.db = DbConnectionService()
def list(self, collection, search):
session = self.db.create_connection(collection)
return list(map(lambda row: {i: str(row[i]) if isinstance(row[i], ObjectId) else row[i] for i in row}, session.find(search))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.