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に変換する方法についてのアイデア。ありがとう
自分で 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とは異なり、「ダンプ」は文字列を返すため、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"
}
}
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
を定義する方がより適切なソリューションであり、要件によって異なります。
これは私が最近エラーを修正した方法です
@app.route('/')
def home():
docs = []
for doc in db.person.find():
doc.pop('_id')
docs.append(doc)
return jsonify(docs)
私は遅れて投稿していることは知っていますが、少なくとも数人に役立つと思いました!
Timとdefuzで言及されている例(どちらも上位投票)は、どちらも完全にうまく機能します。ただし、微妙な違いが時々あります。
Pymongoはjson_utilを提供します-代わりにそれを使用してBSONタイプを処理できます
出力:{"_id":{"$ oid": "abc123"}}
出力:{"_id": "abc123"}
最初の方法は単純に見えますが、どちらの方法も非常に少ない労力で済みます。
Flask
をpymongo
と一緒に使用している人にとって役立つと思うので、ここに投稿します。これは、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"
}
これは、mostアプリケーションよりも作業が面倒ではないと思います。
{
"_id": {"$oid": "5b6b6959828619572d48a9da"},
"created_at": {"$date": 1533837843000}
}
Flaskのjsonifyは、 JSON Security で説明されているようにセキュリティを強化します。 Flaskでカスタムエンコーダーを使用する場合、 JSON Security
「JSONシリアル化不可」エラーを受け取るほとんどのユーザーは、 default=str
を使用するときにjson.dumps
を指定するだけです。例えば:
json.dumps(my_obj, default=str)
これにより、強制的にstr
に変換され、エラーが防止されます。もちろん、生成された出力を見て、必要なものであることを確認します。
レコードの_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)
ソリューション:mongoengine + Marshmallow
mongoengine
およびmarshamallow
を使用する場合、このソリューションがあなたに適用される可能性があります。
基本的に、String
フィールドをMarshmallowからインポートし、デフォルトのSchema id
を上書きしてString
エンコードしました。
from Marshmallow import Schema
from Marshmallow.fields import String
class FrontendUserSchema(Schema):
id = String()
class Meta:
fields = ("id", "email")
受け入れられた答えを改善する追加のソリューションを提供したいと思います。以前に別のスレッドで回答を提供しました here 。
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 __== "__main__":
application.run()
私の場合、次のようなものが必要でした。
class JsonEncoder():
def encode(self, o):
if '_id' in o:
o['_id'] = str(o['_id'])
return o