138

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

4

19 回答 19

177

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

def parse_json(data):
    return json.loads(json_util.dumps(data))
于 2013-08-23T14:41:55.067 回答
154

你应该定义你拥有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)
于 2013-05-16T11:30:54.677 回答
48
>>> 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 替代。

但是这个问题表明我们可以使用 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"
  }
}
于 2014-11-19T18:38:24.763 回答
36

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

json.dumps(my_obj, default=str)

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

于 2019-04-15T20:07:45.330 回答
23
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 对象的示例。你可以试试这个。

于 2016-02-17T10:27:02.403 回答
16

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

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

于 2013-05-16T11:30:50.753 回答
14

在此处发布,因为我认为它可能对使用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)

应用程序.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}
}
于 2018-08-09T18:24:53.420 回答
5

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

class JsonEncoder():
    def encode(self, o):
        if '_id' in o:
            o['_id'] = str(o['_id'])
        return o
于 2019-03-22T09:24:18.057 回答
4

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

    @app.route('/')
    def home():
        docs = []
        for doc in db.person.find():
            doc.pop('_id') 
            docs.append(doc)
        return jsonify(docs)
于 2017-02-06T01:14:09.833 回答
4

对于那些需要通过 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)
于 2020-10-08T16:52:21.800 回答
3

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

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

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

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

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

  1. JsonEncoder 类以我们需要的字符串格式提供相同的输出,并且我们需要另外使用 json.loads(output)。但这会导致

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

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

于 2017-09-12T11:44:47.447 回答
3

你可以试试:

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

于 2021-03-18T12:35:47.660 回答
2

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

于 2015-03-25T10:59:21.033 回答
2

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

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

records = mycollection.find(query, {'_id': 0}) #second argument {'_id':0} unsets the id from the query
for record in records:
    print(record)
于 2018-10-25T05:28:40.140 回答
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()
于 2019-07-05T19:41:41.247 回答
1

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

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

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

于 2020-07-18T19:01:42.540 回答
0

解决方案:mongoengine + 棉花糖

如果您使用mongoengine然后marshamallow此解决方案可能适用于您。

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

from marshmallow import Schema
from marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

    class Meta:
        fields = ("id", "email")
于 2019-03-11T11:35:37.457 回答
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))
于 2020-07-17T18:04:03.747 回答
0

对于那些得到\和不必要的“”

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

  1. 使用json_util.dumps()从 bson 到ObjectId在 BSON 响应中对 JSON 兼容格式进行隐蔽,即"_id": {"$oid": "123456789"}

从上面获得的 JSON Responsejson_util.dumps() 将带有反斜杠和引号

  1. 要删除反斜杠和引号,请使用json.loads()fromjson
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"}]

于 2022-02-10T10:50:10.940 回答