TypeError: ObjectId (”)不是可序列化的 JSON

在使用 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

它是 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 吗。谢谢

179699 次浏览

作为一个快速的替换,你可以改变 {'owner': objectid}{'owner': str(objectid)}

但是定义您自己的 JSONEncoder是一个更好的解决方案,它取决于您的需求。

你应该定义你自己的 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)

Pymongo 提供了 Json _ util-您可以使用它来处理 BSON 类型

def parse_json(data):
return json.loads(json_util.dumps(data))
>>> 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 不同,“ dump”将返回一个字符串,因此它不能用作 Flask 的 jsonify 的1:1替代。

但是 这个问题显示我们可以使用 json _ util 进行序列化。Dump () ,使用 json.load ()转换回 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"
}
}

Flask 的 jsonify 提供了 JSON 安全公司中描述的安全性增强。如果自定义编码器与 Flask 一起使用,则最好考虑 JSON Security 中讨论的要点

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 对象的示例示例。

这就是我最近修复错误的方法

    @app.route('/')
def home():
docs = []
for doc in db.person.find():
doc.pop('_id')
docs.append(doc)
return jsonify(docs)

我知道我发帖晚了,但是我想这至少能帮助一些人!

Tim 和 defuz 提到的这两个例子都非常好用。然而,有时会有一个微小的差异,这个差异可能是显著的。

  1. 下面的方法添加了一个额外的字段,这个字段是冗余的,并且在所有情况下可能并不理想

Pymongo 提供了 json _ util-您可以使用它来处理 BSON 类型

输出: { “ _ id”: { “ $oid”: “ abc123” } }

  1. 而 JsonEncoder 类以字符串格式提供了与我们需要的相同的输出,此外我们还需要使用 json.load (output)。但它会导致

输出: { “ _ id”: “ abc123” }

尽管第一个方法看起来很简单,但是两个方法都只需要很少的工作量。

张贴在这里,因为我认为它可能是有用的人使用 Flaskpymongo。这是我目前的“最佳实践”设置,允许烧瓶马歇尔 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)

App py

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 或 蒙神扩展 JSON

我认为为 mongo 提供特殊的 JSON 会给客户端应用程序带来负担。大多数客户端应用程序不会以任何复杂的方式使用 mongo 对象。如果我使用扩展 json,那么现在我必须使用它服务器端和客户端。ObjectIdTimestamp更容易作为字符串使用,这使得所有这些 mongo 编组疯狂行为都被隔离到服务器。

{
"_id": "5b6b6959828619572d48a9da",
"created_at": "2018-08-08T22:06:17Z"
}

我认为对于 大部分应用程序来说,使用这种方法比。

{
"_id": {"$oid": "5b6b6959828619572d48a9da"},
"created_at": {"$date": 1533837843000}
}

如果不需要记录的 _ id,我建议在查询数据库时取消设置,这样可以直接打印返回的记录

要在查询时取消设置 _ id,然后在循环中打印数据,可以编写如下代码

records = mycollection.find(query, {'_id': 0}) #second argument {'_id':0} unsets the id from the query
for record in records:
print(record)

解决方案: mongoengine + 棉花糖

如果您使用 mongoenginemarshamallow,那么这个解决方案可能适用于您。

基本上,我从棉花糖导入了 String字段,并将默认 Schema id改写为 String编码。

from marshmallow import Schema
from marshmallow.fields import String


class FrontendUserSchema(Schema):


id = String()


class Meta:
fields = ("id", "email")

对我来说,我需要这样的东西:

class JsonEncoder():
def encode(self, o):
if '_id' in o:
o['_id'] = str(o['_id'])
return o

大多数用户在使用 json.dumps时,只需要指定 default=str就可以接收到“ not JSON Series alizable”错误。例如:

json.dumps(my_obj, default=str)

这将强制转换为 str,防止出现错误。当然,然后查看生成的输出以确认它就是您所需要的。

我想提供一个额外的解决方案,改善接受的答案。我以前在另一个线程 给你中提供了答案。

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

如果您不希望 _id响应,您可以重构您的代码,如下所示:

jsonResponse = getResponse(mock_data)
del jsonResponse['_id'] # removes '_id' from the final response
return jsonResponse

这将删除 TypeError: ObjectId('') is not JSON serializable错误。

对于那些需要通过 Jsonify with Flask 返回数据的用户:

cursor = db.collection.find()
data = []
for doc in cursor:
doc['_id'] = str(doc['_id']) # This does the trick!
data.append(doc)
return jsonify(data)

你可以试试:

ObjectId = str (ObjectId (“51948e86c25f4b1d0d303c”))

如果希望将其作为 JSON 响应发送,则需要分两步进行格式化

  1. 使用 json_util.dumps() from BSON 隐藏 BSON 响应中的 ObjectId JSON 兼容格式,即 "_id": {"$oid": "123456789"}

json_util.dumps()获得的上述 JSON 响应将有反斜杠和引号

  1. 要删除反斜杠和引号使用 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"}]