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

Irf*_*fan 93 python json mongodb flask

我在使用Python查询文档上的聚合函数后从MongoDB返回的响应,它返回有效的响应,我可以打印它但不能返回它.

错误:

TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

打印:

{'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}
Run Code Online (Sandbox Code Playgroud)

但是当我试图返回时:

TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

它是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
Run Code Online (Sandbox Code Playgroud)

db连接良好,集合也在那里,我得到了有效的预期结果,但当我尝试返回时,它给了我Json错误.知道如何将响应转换回JOSON.谢谢

tim*_*tim 111

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

  • 这里的一个例子会更有帮助,因为这是最好的方法,但链接文档对于新手来说并不是最友好的用户 (9认同)
  • `from bson import json_util` `json.loads(json_util.dumps(user_collection))` ^ 这在使用 `pipenv install python-bsonjs` 安装 python-bsonjs 后有效 (7认同)
  • +1这是处理BSON的正确方法 (6认同)
  • [这是我如何做到的.](https://github.com/dm-wyncode/flask-mongodb-flpd-rest-api/blob/4ff2b220573dd2bcdc5fb5d86a1be7bca3d34885/resources.py#L15-L16)虽然我得到了预期的结果因为我是Flask和MongoDB的新手,所以我的期望可能会消失!这篇文章有助于澄清之前答案中发生的事情. (3认同)
  • @tim 链接死了... (2认同)

def*_*fuz 99

您应该定义自己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)
Run Code Online (Sandbox Code Playgroud)

也可以通过以下方式使用它.

json.encode(analytics, cls=JSONEncoder)
Run Code Online (Sandbox Code Playgroud)

  • 您可以从bson import ObjectId中添加`,这样每个人都可以更快地复制粘贴吗?谢谢! (2认同)

Gar*_*n S 34

>>> 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"}}]'
Run Code Online (Sandbox Code Playgroud)

来自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
Run Code Online (Sandbox Code Playgroud)

此解决方案将ObjectId和其他(即二进制,代码等)转换为等效字符串,如"$ oid".

JSON输出如下所示:

{
  "_id": {
    "$oid": "abc123"
  }
}
Run Code Online (Sandbox Code Playgroud)


vin*_*rod 18

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)
Run Code Online (Sandbox Code Playgroud)

这是将BSON转换为JSON对象的示例示例.你可以试试这个.


Mos*_*faR 16

作为快速更换,您可以更改{'owner': objectid}{'owner': str(objectid)}.

但定义自己的JSONEncoder是一个更好的解决方案,这取决于您的要求.


nac*_*son 12

在这里发布,因为我认为它可能对使用Flaskwith 的人有用pymongo。这是我目前允许烧瓶编组 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)
Run Code Online (Sandbox Code Playgroud)

应用程序

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
Run Code Online (Sandbox Code Playgroud)

为什么要这样做而不是为 BSON 或mongod 扩展 JSON提供服务?

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

{
  "_id": "5b6b6959828619572d48a9da",
  "created_at": "2018-08-08T22:06:17Z"
}
Run Code Online (Sandbox Code Playgroud)

我认为对于大多数应用程序来说,这比使用它更容易。

{
  "_id": {"$oid": "5b6b6959828619572d48a9da"},
  "created_at": {"$date": 1533837843000}
}
Run Code Online (Sandbox Code Playgroud)


小智 11

对于那些需要使用 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)
Run Code Online (Sandbox Code Playgroud)


小智 9

你可以尝试:

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


Mah*_*rad 6

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

class JsonEncoder():
    def encode(self, o):
        if '_id' in o:
            o['_id'] = str(o['_id'])
        return o
Run Code Online (Sandbox Code Playgroud)

  • +1 哈!能不能更简单一点 一般来说;为了避免自定义编码器和 bson 导入的所有模糊,_将 ObjectID 转换为字符串_:`object['_id'] = str(object['_id'])` (2认同)

Acu*_*nus 6

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

json.dumps(my_obj, default=str)
Run Code Online (Sandbox Code Playgroud)

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

  • 这应该被接受的答案!它彻底解决了 ObjectId 问题。正如 @Asclepius 所说,确保它不会影响其他 json 条目 (2认同)
  • 我同意 - 这既是最简单的答案,也没有(明显的)副作用。 (2认同)

Jcc*_*ria 5

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

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