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

Posted

技术标签:

【中文标题】TypeError: ObjectId(\'\') 不是 JSON 可序列化的【英文标题】:TypeError: ObjectId('') is not JSON serializableTypeError: ObjectId('') 不是 JSON 可序列化的 【发布时间】:2013-05-11 06:32:06 【问题描述】:

我在使用 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。谢谢

【问题讨论】:

【参考方案1】:

Pymongo 提供 json_util - 您可以使用它来处理 BSON 类型

def parse_json(data):
    return json.loads(json_util.dumps(data))

【讨论】:

我同意@tim,这是处理来自 mongo 的 BSON 数据的正确方法。 api.mongodb.org/python/current/api/bson/json_util.html 是的,如果我们使用这种方式似乎更轻松 这实际上是最好的方法。 这里的例子会更有帮助,因为这是最好的方法,但链接的文档对新手来说并不是最友好的 from bson import json_util json.loads(json_util.dumps(user_collection)) ^ 这在使用pipenv install python-bsonjs 安装python-bsonjs 后有效【参考方案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)

【讨论】:

完美!它对我有用。我已经有一个 Json 编码器类,我如何将它与你的类合并?我已经 Json 编码器类是:'class MyJsonEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return str(obj.strftime("%Y-%m-%d %H:%M:%S")) return json.JSONEncoder.default(self, obj)' @IrfanDayan,只需在方法default 中的return 之前添加if isinstance(o, ObjectId): return str(o) 可以加from bson import ObjectId,让大家复制粘贴更快吗?谢谢! @defuz 为什么不直接使用str?这种方法有什么问题? @defuz:当我尝试使用它时,ObjectID 被删除,但我的 json 响应被分解为单个字符。我的意思是当我在 for 循环中从生成的 json 打印每个元素时,我将每个字符作为一个元素。知道如何解决这个问题吗?【参考方案3】:
>>> 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替换。

但是this question 表明我们可以使用 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"
  

【讨论】:

澄清一下,不需要直接从 Flask 请求处理程序调用 'jsonify' - 只需返回经过清理的结果。 你说得对。 Python dict(json.loads 返回的)应该被 Flask 自动 json 化。 不是dict对象不可调用吗? @rick112358 无法调用的字典与此问答有何关系? 您也可以使用 json_util.loads() 来获取完全相同的字典(而不是使用 '$oid' 键的字典)。【参考方案4】:

大多数收到“not JSON serializable”错误的用户只需要在使用json.dumps时指定default=str。例如:

json.dumps(my_obj, default=str)

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

【讨论】:

【参考方案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】:

在这里发帖,因为我认为它可能对使用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 或 mongod extended JSON?

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


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

我认为对于大多数应用程序来说,这比使用起来更轻松。


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

【讨论】:

【参考方案8】:

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

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

【讨论】:

在这种情况下,您没有传递 '_id' 属性,而是删除了 '_id' 并传递了 doc 的其他属性【参考方案9】:

在我的情况下,我需要这样的东西:

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

【讨论】:

+1 哈!可以更简单吗?一般来说;为了避免自定义编码器和 bson 导入的所有模糊,将 ObjectID 转换为字符串object['_id'] = str(object['_id'])【参考方案10】:

我知道我发帖迟了,但我认为这至少会对一些人有所帮助!

tim 和 defuz 提到的两个例子(投票率最高)都非常好。但是,有时可能会有很大的细微差别。

    以下方法添加了一个额外的字段,该字段是多余的,可能并非在所有情况下都理想

Pymongo 提供 json_util - 你可以使用它来处理 BSON 类型

输出: “_ID”: “$oid”:“abc123”

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

输出: “_id”:“abc123”

尽管第一种方法看起来很简单,但这两种方法都需要很少的努力。

【讨论】:

这对于pytest-mongodb 插件在创建灯具时非常有用【参考方案11】:

对于那些需要通过 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)

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

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

要在查询时取消设置 _id,然后在循环中打印数据,您可以编写类似这样的内容

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

【讨论】:

【参考方案14】:

我想提供一个额外的解决方案来改进接受的答案。我之前在另一个帖子here中提供了答案。

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】:

你可以试试:

objectid = str(ObjectId("51948e86c25f4b1d1c0d303c"))

【讨论】:

【参考方案16】:

如果您不希望 _id 响应,您可以像这样重构您的代码:

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

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

【讨论】:

【参考方案17】:

解决方案:mongoengine + marshmallow

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

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

from marshmallow import Schema
from marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

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

【讨论】:

【参考方案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))

【讨论】:

以上是关于TypeError: ObjectId('') 不是 JSON 可序列化的的主要内容,如果未能解决你的问题,请参考以下文章

Node.js TypeError无法读取未定义的属性objectId

Mongo ObjectID:即使使用 pytz,“也无法比较原始偏移量和可感知偏移量的日期时间”

findOneAndUpdate 不创建 ObjectId

当查询字段为objectId时,geoNear聚合是不是不起作用?

核心数据 - 在不触发错误的情况下获取对多关系的 objectID

Mongoose JS - 填充方法不返回完整引用的对象... - 仅返回 objectId 版本