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 70

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)

Pymongo предоставляет json_util - вы можете использовать его вместо того, чтобы обрабатывать типы BSON

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

Я знаю, что пишу поздно, но думал, что это поможет, по крайней мере, нескольким людям!

оба примера, упомянутые Тимом и дефузом (которые являются лучшими голосами), отлично работают. Однако есть небольшая разница, которая может быть значительной в разы.

  1. следующий метод добавляет одно дополнительное поле, которое является избыточным и не может быть идеальным во всех случаях

Pymongo обеспечивает json_util - вы можете использовать один вместо того, чтобы обрабатывать зубров типы

выход: { "_идентификатор": { "$oid": "abc123" } }

  1. где класс 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}
}