【问题标题】: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。谢谢

【问题讨论】:

    标签: python json mongodb flask


    【解决方案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 提到的两个例子(投票率最高)都非常好。但是,有时可能会有很大的细微差别。

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

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

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

            1. 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))
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2017-10-24
                            • 2016-08-02
                            • 2019-02-08
                            • 2018-01-18
                            • 2014-08-13
                            • 2019-09-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多