TypeError:ObjectId('')不是 JSON 可序列化的
- 2025-03-04 08:25:00
- admin 原创
- 57
问题描述:
我使用 Python 查询文档上的聚合函数后从 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
这是 RESTful 调用:
@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 吗?谢谢
解决方案 1:
PyMongo 发行版中的Bson提供了json_util - 你可以使用它来处理 BSON 类型
from bson import json_util
def parse_json(data):
return json.loads(json_util.dumps(data))
解决方案 2:
你应该定义你自己的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)
解决方案 3:
大多数收到“非 JSON 序列化”错误的用户只需default=str
在使用时指定即可json.dumps
。例如:
json.dumps(my_obj, default=str)
这将强制转换为str
,从而避免错误。当然,然后查看生成的输出以确认它是您所需要的。
解决方案 4:
>>> 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"
}
}
解决方案 5:
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 对象的示例。你可以尝试一下。
解决方案 6:
作为快速替换,您可以更改{'owner': objectid}
为{'owner': str(objectid)}
。
但定义您自己的JSONEncoder
是一个更好的解决方案,这取决于您的要求。
解决方案 7:
我觉得它可能对使用 的人有用,所以在这里发布Flask
。pymongo
这是我当前的“最佳实践”设置,允许 Flask 编组 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}
}
解决方案 8:
对于那些需要使用 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)
解决方案 9:
您可以尝试:
对象 ID = str(ObjectId("51948e86c25f4b1d1c0d303c"))
解决方案 10:
就我而言,我需要这样的东西:
class JsonEncoder():
def encode(self, o):
if '_id' in o:
o['_id'] = str(o['_id'])
return o
解决方案 11:
如果要将其作为 JSON 响应发送,则需要分两步进行格式化
使用
json_util.dumps()
bson 将ObjectId
BSON 响应转换为 JSON 兼容格式"_id": {"$oid": "123456789"}
上述 JSON 响应将json_util.dumps()
包含反斜杠和引号
要删除反斜杠和引号,请
json.loads()
使用json
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"}]
解决方案 12:
这是我最近修复错误的方法
@app.route('/')
def home():
docs = []
for doc in db.person.find():
doc.pop('_id')
docs.append(doc)
return jsonify(docs)
解决方案 13:
我知道我发帖晚了但我认为它至少会帮助一些人!
tim 和 defuz 提到的示例(得票最多)都运行良好。但是,它们之间存在细微的差异,有时这种差异可能非常显著。
以下方法添加了一个额外的字段,该字段是多余的,并且可能并不适用于所有情况
Pymongo 提供了 json_util - 你可以使用它来处理 BSON 类型
输出: { “_id”:{ “$oid”:“abc123” } }
而 JsonEncoder 类以我们需要的字符串格式提供相同的输出,我们还需要使用 json.loads(output)。但这会导致
输出:{“_id”:“abc123”}
尽管第一种方法看起来很简单,但这两种方法都不需要付出太多努力。
解决方案 14:
我想提供一个额外的解决方案来改进已接受的答案。我之前已在另一个帖子中提供了答案。
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()
解决方案 15:
如果您不需要记录的 _id,我建议在查询数据库时取消设置它,这将使您能够直接打印返回的记录,例如
要在查询时取消设置 _id,然后循环打印数据,你可以这样写
records = mycollection.find(query, {'_id': 0}) #second argument {'_id':0} unsets the id from the query
for record in records:
print(record)
解决方案 16:
Flask 的 jsonify 提供了JSON 安全中所述的安全增强功能。如果在 Flask 中使用自定义编码器,最好考虑JSON 安全中讨论的要点
解决方案 17:
如果您不想_id
回应,您可以重构您的代码,如下所示:
jsonResponse = getResponse(mock_data)
del jsonResponse['_id'] # removes '_id' from the final response
return jsonResponse
这将消除TypeError: ObjectId('') is not JSON serializable
错误。
解决方案 18:
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))
解决方案 19:
@app.route('/users', methods = ['GET'])
def get_users():
online_users = mongo.db.users.find({})
list_users = list(online_users)
json_users = json.dumps(list_users, default=json_util.default)
return Response(json_users, mimetype='application/json')
这就是我让它工作的方式
解决方案 20:
解决方案:mongoengine + marshmallow
如果您使用mongoengine
,那么marshamallow
该解决方案可能适合您。
基本上,我String
从 marshmallow 导入了字段,并覆盖了默认Schema id
的String
编码。
from marshmallow import Schema
from marshmallow.fields import String
class FrontendUserSchema(Schema):
id = String()
class Meta:
fields = ("id", "email")
扫码咨询,免费领取项目管理大礼包!