web-dev-qa-db-fra.com

TypeError: ObjectId ('') n'est pas sérialisable JSON

Ma réponse de MongoDB après avoir interrogé une fonction agrégée sur un document utilisant Python, elle renvoie une réponse valide et je peux l’imprimer, mais ne peut pas la retourner.

Erreur:

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

Impression:

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

db est bien connecté et la collection est là aussi et je suis revenu résultat attendu valide, mais lorsque j'essaie de retourner, il me donne une erreur Json. Toute idée sur la manière de reconvertir la réponse en JSON. Merci

89
Irfan Dayan

Vous devriez définir vous-même JSONEncoder et l'utiliser:

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)

Il est également possible de l'utiliser de la manière suivante.

json.encode(analytics, cls=JSONEncoder)
93
defuz

Pymongo fournit json_util - vous pouvez utiliser celui-là à la place pour gérer les types BSON

107
tim
>>> 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"}}]'

Exemple réel de json_util .

Contrairement à jsonify de Flask, "dumps" renverra une chaîne, elle ne peut donc pas être utilisée pour remplacer 1: 1 de jsonify de Flask.

Mais cette question montre que nous pouvons sérialiser à l'aide de json_util.dumps (), reconvertir en dict en utilisant json.loads () et enfin appeler le jsonify de Flask dessus.

Exemple (dérivé de la réponse à la question précédente):

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

Cette solution convertira ObjectId et les autres utilisateurs (c-à-d. Binary, Code, etc.) en une chaîne équivalente telle que "$ oid".

La sortie JSON ressemblerait à ceci:

{
  "_id": {
    "$oid": "abc123"
  }
}
32
Garren S
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)

Ceci est l'exemple de conversion de BSON en objet JSON. Vous pouvez l'essayer.

17
vinit kantrod

En remplacement rapide, vous pouvez remplacer {'owner': objectid} Par {'owner': str(objectid)}.

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

16
MostafaR

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)
5
Jcc.Sanabria

Je sais que je poste tardivement, mais je pense que cela aiderait au moins quelques personnes!

Les exemples cités par tim et defuz (qui ont obtenu le vote le plus élevé) fonctionnent parfaitement. Cependant, il existe une différence minime qui peut parfois être importante.

  1. La méthode suivante ajoute un champ supplémentaire qui est redondant et peut ne pas être idéal dans tous les cas.

Pymongo fournit json_util - vous pouvez l'utiliser à la place pour gérer les types BSON

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

  1. Where en tant que classe JsonEncoder donne la même sortie dans le format de chaîne que nous avons besoin et nous devons utiliser json.loads (sortie) en plus. Mais cela mène à

Sortie: {"_id": "abc123"}

Bien que la première méthode semble simple, elle nécessite un effort minimal.

3
rohithnama

Publier ici car je pense que cela peut être utile pour les personnes utilisant Flask avec pymongo. C’est ma configuration actuelle "de bonne pratique" pour autoriser les types de données flask) à marshall phamongo 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

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

Je pense que servir Jong spécial JSON impose un fardeau aux applications clientes. La plupart des applications clientes ne s'inquièteront pas de l'utilisation complexe d'objets mongo. Si je sers json étendu, je dois maintenant l’utiliser côté serveur et côté client. ObjectId et Timestamp sont plus faciles à manipuler en tant que chaînes, ce qui maintient toute cette folie mongo marshalling en quarantaine sur le serveur.

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

Je pense que cela est moins onéreux de travailler avec la plupart des applications .

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

Jsonify de Flask fournit une amélioration de la sécurité comme décrit dans Sécurité JSON . Si un codeur personnalisé est utilisé avec Flask, il est préférable de prendre en compte les points abordés dans le sécurité JSON

2
Anish

La plupart des utilisateurs qui reçoivent l'erreur "not JSON serializable" doivent simplement spécifier default=str en utilisant json.dumps . Par exemple:

json.dumps(my_obj, default=str)

Cela forcera une conversion en str, empêchant ainsi l’erreur. Bien sûr, regardez ensuite la sortie générée pour confirmer que c'est ce dont vous avez besoin.

2
Acumenus

Si vous n’avez pas besoin de l’identifiant _id des enregistrements, je vous recommanderai de le désactiver lors de l’interrogation de la base de données, ce qui vous permettra d’imprimer directement les enregistrements renvoyés, par exemple.

Pour supprimer le _id lors de l'interrogation, puis imprimer des données en boucle, vous écrivez quelque chose comme ceci

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

SOLUTION pour: mongoengine + guimauve

Si vous utilisez mongoengine et marshamallow, alors cette solution pourrait vous être applicable.

Fondamentalement, j'ai importé le champ String de Marshmallow et écrasé le champ par défaut Schema id être String codé.

from Marshmallow import Schema
from Marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

    class Meta:
        fields = ("id", "email")
0
Lukasz Dynowski

Je voudrais fournir une solution supplémentaire qui améliore la réponse acceptée. J'ai déjà fourni les réponses dans un autre fil ici .

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

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
0
Mahorad