146 votes

Erreur de type : ObjectId('') n'est pas sérialisable en JSON

Ma réponse en retour de MongoDB après avoir interrogé une fonction agrégée sur le document en utilisant Python, il retourne une réponse valide et je peux l'imprimer mais pas la retourner.

Erreur :

TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable

Imprimer :

{'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}

Mais quand j'essaie de revenir :

TypeError: ObjectId('51948e86c25f4b1d1c0d303c') is not JSON serializable

C'est un appel 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

La base de données est bien connectée et la collection est là aussi. J'ai obtenu le résultat valide attendu mais quand j'essaie de retourner le résultat, cela me donne une erreur Json. Avez-vous une idée de la façon de reconvertir la réponse en JSON ? Merci

17voto

MostafaR Points 1832

En guise de remplacement rapide, vous pouvez changer {'owner': objectid} a {'owner': str(objectid)} .

Mais définir votre propre JSONEncoder est une meilleure solution, cela dépend de vos besoins.

15voto

nackjicholson Points 859

Je le poste ici car je pense qu'il peut être utile pour les personnes qui utilisent la Flask con pymongo . Il s'agit de ma configuration actuelle de "meilleure pratique" pour permettre à flask de marshaller les types de données bson de pymongo.

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

Pourquoi faire cela au lieu de servir BSON ou JSON étendu de mongod ?

Je pense que le fait de servir du JSON spécial Mongo fait peser une charge sur les applications clientes. La plupart des applications clientes ne se soucient pas d'utiliser les objets mongo de manière complexe. Si je sers du JSON étendu, je dois maintenant l'utiliser côté serveur et côté client. ObjectId y Timestamp sont plus faciles à utiliser en tant que chaînes de caractères et cela permet de garder toute cette folie de marshalling mongo en quarantaine sur le serveur.

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

Je pense que c'est moins onéreux de travailler avec pour le plus que les applications.

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

8voto

Adeoy Points 51

Pour ceux qui ont besoin de retourner les données par Jsonify avec Flask :

cursor = db.collection.find()
data = []
for doc in cursor:
    doc['_id'] = str(doc['_id']) # This does the trick!
    data.append(doc)
return jsonify(data)

6voto

Mahorad Points 577

Dans mon cas, j'avais besoin de quelque chose comme ça :

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

4voto

Jcc.Sanabria Points 149

Voici comment j'ai récemment corrigé l'erreur

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X