TypeError: ObjectId (") не является сериализуемым JSON
мой ответ обратно из MongoDB после запроса агрегированной функции на документ с помощью Python, он возвращает действительный ответ, и я могу распечатать его, но не могу вернуть его.
ошибка:
TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable
Print:
{'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
это спокойный звонок:
@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. Любая идея, как преобразовать ответ обратно в JOSON. Спасибо
9 ответов:
вы должны определить, что вы владеете
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.
В отличие от Jsonify колбы, "дампы" вернут строку, поэтому ее нельзя использовать в качестве замены 1:1 Jsonify колбы.
но этот вопрос показывает, что мы можем сериализовать с помощью json_util.dumps (), конвертировать обратно в dict с помощью json.загружает () и, наконец, вызывает 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 BSON 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)
Я знаю, что пишу поздно, но думал, что это поможет, по крайней мере, нескольким людям!
оба примера, упомянутые Тимом и дефузом (которые являются лучшими голосами), отлично работают. Однако есть небольшая разница, которая может быть значительной в разы.
- следующий метод добавляет одно дополнительное поле, которое является избыточным и не может быть идеальным во всех случаях
Pymongo обеспечивает json_util - вы можете использовать один вместо того, чтобы обрабатывать зубров типы
выход: { "_идентификатор": { "$oid": "abc123" } }
- где класс JsonEncoder дает тот же вывод в строковом формате, что и нам, и нам нужно использовать json.нагрузки (выход) в добавлении. Но это приводит к
выход: { "Параметр _id": "абв123" }
несмотря на то, что первый метод выглядит простым, оба метода требуют очень минимальных усилий.
фляги jsonify обеспечивает повышение безопасности, как описано в JSON Security. Если пользовательский кодер используется с колбой, его лучше рассмотреть пункты, обсуждаемые в JSON Security
размещение здесь, как я думаю, это может быть полезно для людей, использующих
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 extended JSON?
Я думаю, что обслуживание mongo special JSON накладывает нагрузку на клиентские приложения. Большинство клиентов приложения не будут заботиться об использовании объектов mongo каким-либо сложным образом. Если я обслуживаю расширенный json, теперь я должен использовать его на стороне сервера и на стороне клиента.
ObjectId
иTimestamp
легче работать со строками, и это держит все это безумие монго маршалинга на карантине на сервере.{ "_id": "5b6b6959828619572d48a9da", "created_at": "2018-08-08T22:06:17Z" }
Я думаю, что это менее обременительно работать с большинство приложения, чем.
{ "_id": {"$oid": "5b6b6959828619572d48a9da"}, "created_at": {"$date": 1533837843000} }