TypeError: ObjectId ('') не является сериализуемым JSON

Мой ответ от MongoDB после запроса агрегированной функции к документу с использованием Python, он возвращает действительный ответ, и я могу напечатать его, но не могу его вернуть.

Ошибка:

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

Это ОТЛИЧНЫЙ звонок:

@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. Спасибо

Ответ 1

Вы должны определить свой 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)

Ответ 2

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

Ответ 3

>>> 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(), преобразовать обратно в dict с помощью json.loads() и, наконец, вызвать 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 и другие (т.е. Binary, Code и т.д.) в эквивалент строки, такой как "$ oid".

Выход JSON будет выглядеть следующим образом:

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

Ответ 4

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. Вы можете попробовать это.

Ответ 5

В качестве быстрой замены вы можете изменить {'owner': objectid} на {'owner': str(objectid)}.

Но определение вашего собственного JSONEncoder - лучшее решение, оно зависит от ваших требований.

Ответ 6

Вот как я недавно исправил ошибку

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

Ответ 7

Я знаю, что я опаздываю, но думал, что это поможет хотя бы нескольким людям!

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

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

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

Выход: { "_Я бы": {    "$ oid": "abc123" } }

  1. Где, как класс JsonEncoder, дает тот же результат в строчном формате, что и нам, и нам нужно также использовать json.loads(output). Но это приводит к

Выход: { "_id": "abc123" }

Несмотря на то, что первый метод выглядит просто, оба метода требуют очень минимальных усилий.

Ответ 8

Публикация здесь, как я думаю, может быть полезна людям, использующим 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

Почему это вместо того, чтобы обслуживать расширенный JSON или BSON?

Я думаю, что использование специального JSON-монго накладывает бремя на клиентские приложения. Большинство клиентских приложений не заботятся об использовании монго-объектов любым сложным способом. Если я обслуживаю расширенный JSON, теперь я должен использовать его как на стороне сервера, так и на стороне клиента. ObjectId и Timestamp легче работать как со строками, и это сохраняет все это монологическое безумие в карантине на сервере.

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

Я думаю, что это менее обременительно для работы с большинством приложений, чем.

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

Ответ 9

Flask jsonify обеспечивает повышение безопасности, как описано в JSON Security. Если пользовательский кодер используется с Flask, лучше рассмотреть пункты, описанные в JSON Security

Ответ 10

Если вам не понадобится _id записей, я рекомендую отключить его при запросе к БД, что позволит вам напечатать возвращенные записи напрямую, например

Чтобы сбросить _id при запросе, а затем распечатать данные в цикле, вы пишете что-то вроде этого

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

Ответ 11

Большинству пользователей, которые получают ошибку "not JSON serializable", просто нужно указать default=str при использовании json.dumps. Например:

json.dumps(my_obj, default=str)

Это заставит преобразование в str, предотвращая ошибку. Конечно, затем посмотрите на сгенерированный вывод, чтобы убедиться, что это то, что вам нужно.

Ответ 12

РЕШЕНИЕ для: mongoengine + зефир

Если вы используете mongoengine и marshamallow то это решение может быть применимо для вас.

По сути, я импортировал поле String из зефира и переписал Schema id умолчанию, чтобы он был закодирован в виде String.

from marshmallow import Schema
from marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

    class Meta:
        fields = ("id", "email")

Ответ 13

в моем случае мне нужно что-то вроде этого:

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

Ответ 14

Я хотел бы предоставить дополнительное решение, которое улучшает принятый ответ. Я ранее предоставил ответы в другой теме здесь.

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