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

dbは適切に接続されており、コレクションもあり、有効な期待される結果が返されましたが、返そうとすると、Jsonエラーが表示されます。応答をJSONに戻す方法についてのアイデア。ありがとう

回答:


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エンコーダクラスを持っていますが、それをあなたのクラスとどのようにマージできますか? str(obj.strftime( "%Y-%m-%d%H:%M:%S"))return json.JSONEncoder.default(self、obj) '
Irfan

1
@IrfanDayan、メソッドのif isinstance(o, ObjectId): return str(o)returnに追加するだけdefaultです。
defuz

2
を追加してもらえますfrom bson import ObjectIdか?そうすれば、誰もがより速くコピーして貼り付けることができますか?ありがとう!
Liviu Chircu

@defuzなぜ使用しないのstrですか?そのアプローチの何が問題になっていますか?
ケビン

@defuz:これを使用しようとすると、ObjectIDは削除されますが、json応答が単一の文字に分割されます。つまり、forループで結果のjsonから各要素を出力すると、各文字が要素として取得されます。これを解決する方法はありますか?
Varij Kapil

119

Pymongoが提供するます -代わりにそれを使用してBSONタイプを処理できます


@timに同意します。これは、mongoからのBSONデータを処理する正しい方法です。 api.mongodb.org/python/current/api/bson/json_util.html
Joshua Powell

はい、この方法を使用すれば、より手間のかからないようです
jonprasetyo

それが実際に最善の方法です。
Rahul

14
これは最良の方法ですが、リンクされたドキュメントは初心者にとって最もユーザーフレンドリーではないため、ここでの例はもう少し役立つでしょう
Jake

2
from bson import json_util json.loads(json_util.dumps(user_collection)) ^これはpython-bsonjsをインストールした後に機能しました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およびその他(つまり、バイナリ、コードなど)を「$ oid」などの同等の文字列に変換します。

JSON出力は次のようになります。

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

明確にするために、Flaskリクエストハンドラーから直接「jsonify」を呼び出す必要はありません。サニタイズされた結果を返すだけです。
oferei

あなたは絶対的に正しいです。Python dict(json.loadsが返す)は、Flaskによって自動的にjson化されます。
Garren S

dictオブジェクトは呼び出し不可能ではありませんか?
SouvikMaji 2017

@ rick112358呼べないdictはこのQ&Aにどのように関係しますか?
Garren S

また、json_util.loads()を使用して、(「$ oid」キーを持つ辞書の代わりに)まったく同じ辞書を取得することもできます。
rGun 2017年

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オブジェクトに変換するサンプルの例です。これを試すことができます。


21

「not JSON serializable」エラーを受け取ったほとんどのユーザーはdefault=str、を使用するときに指定するだけですjson.dumps。例えば:

json.dumps(my_obj, default=str)

これにより強制的にに変換されstr、エラーが防止されます。もちろん、生成された出力を見て、それが必要なものであることを確認してください。


16

迅速な交換として、あなたは変更することができます{'owner': objectid}{'owner': str(objectid)}

しかし、独自のものを定義JSONEncoderする方がより良いソリューションであり、それは要件によって異なります。


6

ここで投稿することは、でを使用Flaskして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)

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

3

私は遅く投稿しているのを知っていますが、それが少なくとも数人の人々を助けるだろうと思いました!

timとdefuz(トップ投票)で言及されている例はどちらも完全に問題なく動作します。ただし、微妙な違いがあり、時々重要になる場合があります。

  1. 次のメソッドは、余分なフィールドを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 Ha!もっと簡単になったでしょうか😍一般的に言って; カスタムエンコーダとBSONのインポートを持つすべてのファズを避けるために、文字列にオブジェクトIDをキャストobject['_id'] = str(object['_id'])
Vexy


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が必要ない場合は、DBをクエリするときに設定を解除することをお勧めします。これにより、返されたレコードを直接印刷できます。

クエリ時に_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エラーが解消されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.