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


109

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

数据库连接良好,集合也在那里,我得到了有效的预期结果,但是当我尝试返回时,它给了我Json错误。任何想法如何将响应转换回JSON。谢谢

Answers:


118

您应该定义自己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编码的类是:'类MyJsonEncoder(json.JSONEncoder):def default(self,obj):if isinstance(obj,datetime):return str(obj.strftime(“%Y-%m-%d%H:%M:%S”))返回json.JSONEncoder.default(self,obj)'
Irfan

1
@IrfanDayan,只需在method中添加if isinstance(o, ObjectId): return str(o)之前。returndefault
defuz,

2
您可以添加from bson import ObjectId,这样每个人都可以更快地复制粘贴吗?谢谢!
Liviu Chircu

@defuz为什么不只是使用str?这种方法有什么问题?
凯文

@defuz:当我尝试使用它时,ObjectID被删除,但是我的json响应被分解为单个字符。我的意思是,当我在for循环中从结果json打印每个元素时,我会将每个字符作为元素。任何想法如何解决这个问题?
Varij Kapil

119

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


我同意@tim,这是处理来自mongo的BSON数据的正确方法。 api.mongodb.org/python/current/api/bson/json_util.html
Joshua Powell,

是的,如果使用这种方式,似乎更
省事

那实际上是最好的方法。
拉胡尔

14
这里的示例可能会有所帮助,因为这是最好的方法,但是链接文档对于菜鸟来说并不是最友好的用户
Jake

2
from bson import json_util json.loads(json_util.dumps(user_collection)) ^使用pipenv install python-bsonjs
NBhat

38
>>> 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不同,“转储”将返回一个字符串,因此不能用作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和其他代码(例如Binary,Code等)转换为等效的字符串,例如“ $ oid”。

JSON输出如下所示:

{
  "_id": {
    "$oid": "abc123"
  }
}

只是为了澄清,不需要直接从Flask请求处理程序中调用“ jsonify”-只需返回经过清理的结果即可。
oferei

你是绝对正确的。Flask应该自动对Python字典(json.loads返回)进行json化。
Garren S

字典对象不是不可调用的吗?
SouvikMaji '17

@ rick112358不可调用的字典与此问答有何关系?
Garren S

您还可以使用json_util.loads()获取完全相同的字典(而不是带有'$ oid'键的字典)。
rGun

22

收到“无法JSON序列化”错误的大多数用户只需default=str使用即可指定json.dumps。例如:

json.dumps(my_obj, default=str)

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


21
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对象的示例示例。你可以试试看


16

作为快速替代品,您可以更改{'owner': objectid}{'owner': str(objectid)}

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


6

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

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扩展JSON

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

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

我认为,与大多数应用程序相比,这要轻得多。

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

4

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

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

在这种情况下,您没有传递'_id'属性,而是删除了'_id'并传递了doc的其他属性
Muhriddin Ismoilov

3

我知道我发帖太晚了,但我认为这至少可以帮助到一些人!

tim和defuz提到的两个示例(都获得最高投票)都可以很好地工作。但是,有时会有细微的差别,这有时可能很重要。

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

Pymongo提供了json_util-您可以改用那个来处理BSON类型

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

  1. 在JsonEncoder类以所需的字符串格式给出相同输出的情况下,我们还需要使用json.loads(output)。但这导致

输出:{“ _id”:“ abc123”}

即使第一种方法看起来很简单,但这两种方法都需要非常少的精力。


这对于pytest-mongodb创建固定装置时的插件非常有用
tsveti_iko

3

就我而言,我需要这样的东西:

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

1
+1哈!难道会更简单吗?为避免使用自定义编码器和bson导入带来的所有object['_id'] = str(object['_id'])
毛刺,请将


2

我想提供一个附加的解决方案,以改善公认的答案。我以前在这里的另一个线程中提供了答案。

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

1

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

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

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

0

解决方案: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")

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

0

如果您不想_id回应,可以重构代码,如下所示:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.