簡體   English   中英

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

[英]TypeError: ObjectId('') is not JSON serializable

在使用 Python 查詢文檔上的聚合 function 后,我從 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。 謝謝

Pymongo提供了json_util - 你可以使用它來處理 BSON 類型

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

您應該定義自己的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)
>>> 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"
  }
}

大多數收到“not JSON serializable”錯誤的用戶只需要在使用json.dumps時指定default=str 例如:

json.dumps(my_obj, default=str)

這將強制轉換為str ,從而防止錯誤。 當然,然后查看生成的輸出以確認它是您需要的。

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 對象的示例示例。 你可以試試這個。

作為快速替換,您可以將{'owner': objectid}更改為{'owner': str(objectid)}

但是定義您自己的JSONEncoder是一個更好的解決方案,這取決於您的要求。

在這里發布,因為我認為它可能對使用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)

應用程序

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,現在我必須在服務器端和客戶端使用它。 ObjectIdTimestamp作為字符串更容易使用,這使所有這些 mongo 編組瘋狂隔離到服務器。

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

我認為對於大多數應用程序來說,這比使用它更容易。

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

這就是我最近修復錯誤的方式

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

就我而言,我需要這樣的東西:

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

我知道我發布晚了,但我認為這至少會幫助一些人!

tim 和 defuz 提到的兩個例子(投票最高)都運行良好。 但是,有時可能存在顯着的微小差異。

  1. 以下方法添加了一個額外的字段,該字段是多余的,可能並非在所有情況下都理想

Pymongo 提供了 json_util - 你可以使用它來處理 BSON 類型

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

  1. 因為 JsonEncoder 類以我們需要的字符串格式提供相同的輸出,我們還需要使用 json.loads(output) 。 但它導致

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

盡管第一種方法看起來很簡單,但這兩種方法都需要很少的努力。

如果要將其作為 JSON 響應發送,則需要分兩步進行格式化

  1. 使用來自 bson 的json_util.dumps()來隱藏 BSON 中的ObjectId響應 JSON 兼容格式,即"_id": {"$oid": "123456789"}

上述從json_util.dumps()獲得的 JSON 響應將帶有反斜杠和引號

  1. 要刪除反斜杠和引號,請使用 json 中的json json.loads()
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"}]

Flask 的 jsonify 提供了安全性增強,如JSON Security 中所述 如果自定義編碼器與 Flask 一起使用,最好考慮JSON 安全性中討論的要點

我想提供一個額外的解決方案來改進已接受的答案。 我以前在這里的另一個線程中提供了答案。

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

對於那些需要使用 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)

如果您不需要記錄的 _id,我建議您在查詢數據庫時取消設置它,這將使您能夠直接打印返回的記錄,例如

要在查詢時取消設置 _id 然后在循環中打印數據,您可以編寫如下內容

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

如果你不想要_id響應,你可以像這樣重構你的代碼:

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

這將刪除TypeError: ObjectId('') is not JSON serializable錯誤。

你可以試試:

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM