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
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)
>>> 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"
}
}
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.
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.
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)
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.
Pymongo fournit json_util - vous pouvez l'utiliser à la place pour gérer les types BSON
Sortie: {"_id": {"$ oid": "abc123"}}
Sortie: {"_id": "abc123"}
Bien que la première méthode semble simple, elle nécessite un effort minimal.
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}
}
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
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.
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)
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")
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()
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