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 en utilisant Python, elle retourne une réponse valide et je peux l'imprimer mais ne peux 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'essaie de revenir:

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

il est 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 collecte est là aussi et je suis de retour valide résultat attendu mais quand j'essaie de revenir, il me donne erreur Json. Une idée pour transformer la réponse en JOSON. Merci

70
demandé sur Jcc.Sanabria 2013-05-16 15:25:17

9 réponses

vous devez définir votre propre 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)
79
répondu defuz 2017-03-24 16:35:46

Pymongo provides json_util - vous pouvez utiliser celui-ci à la place pour manipuler les types bson

90
répondu tim 2013-08-23 14:41:55
>>> 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 actuel de json_util .

contrairement à Jsonify de Flask, "dumps" retournera une chaîne, il ne peut donc pas être utilisé comme un remplacement 1:1 de jsonify de Flask.

mais cette question montre que nous pouvons sérialiser en utilisant json_util.dumps (), convertissez de nouveau en DCT en utilisant json.charges() et enfin appeler Flacon de jsonify sur elle.

exemple réponse à la question):

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 d'autres (C'est-à-dire binaire, Code, etc.) en un équivalent chaîne tel que" $oid."

sortie JSON ressemblerait à ceci:

{
  "_id": {
    "$oid": "abc123"
  }
}
21
répondu Garren S 2017-05-23 12:02:50
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)

ceci est l'exemple pour convertir BSON en objet JSON. Vous pouvez essayer cette.

13
répondu vinit kantrod 2016-07-13 01:08:03

pour un 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.

12
répondu MostafaR 2013-05-16 11:30:50

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
répondu Jcc.Sanabria 2017-02-06 01:14:09

je sais que je poste tard, mais j'ai pensé que cela aiderait au moins quelques personnes!

les Deux exemples mentionnés par tim et defuz(voté) fonctionne parfaitement bien. Cependant, il y a une différence infime qui pourrait être significative à certains moments.

  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 utiliser celui-là à la place de gérer les types BSON

sortie: { "_ID": { "$oid": "abc123" } }

  1. où comme la classe JsonEncoder donne la même sortie dans le format de chaîne que nous avons besoin et nous avons besoin d'utiliser json.charges(sortie) en plus. Mais il conduit à

sortie: { "_id": "abc123" }

même si la première méthode semble simple, les deux méthodes nécessitent un effort minime.

3
répondu rohithnama 2017-09-12 11:44:47

Flacon de jsonify fournit à l'amélioration de la sécurité, comme décrit dans Sécurité JSON . Si l'encodeur personnalisé est utilisé avec le flacon, il est préférable de considérer points discutés dans le JSON Security

2
répondu Anish 2015-09-03 07:24:21

affichage ici que je pense qu'il peut être utile pour les gens qui utilisent Flask avec pymongo . Il s'agit de ma configuration actuelle "best practice" pour permettre flask à marshall pymongo BSON types de données.

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 au lieu de servir BSON ou mongod extended JSON ?

je pense que servir mongo special JSON met un fardeau sur les applications client. La plupart des applications client ne se soucieront pas de l'utilisation des objets mongo d'une manière complexe. Si je sers étendu json, j'ai maintenant l'utiliser côté serveur et côté client. ObjectId et Timestamp sont plus faciles à utiliser comme cordes et cela maintient toute cette folie de Mongo marshalling 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 la plupart applications que.

{
  "_id": {"$oid": "5b6b6959828619572d48a9da"},
  "created_at": {"$date": 1533837843000}
}
0
répondu nackjicholson 2018-08-09 18:24:53