it-swarm.com.de

TypeError: ObjectId ('') ist nicht mit JSON serialisierbar

Meine Antwort von MongoDB nach dem Abfragen einer aggregierten Funktion für ein Dokument mit Python zurück. Es gibt eine gültige Antwort zurück und ich kann sie drucken, aber nicht zurückgeben.

Error:

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

Drucken:

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

Aber wenn ich versuche zurückzukehren:

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

Es ist ein vollständiger Aufruf:

@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 ist gut verbunden und Sammlung ist auch da und ich habe gültiges erwartetes Ergebnis zurückbekommen, aber wenn ich versuche zurückzugeben, gibt es mir Json-Fehler. Eine Idee, wie die Antwort wieder in JSON konvertiert werden kann. Vielen Dank

89
Irfan Dayan

Sie sollten definieren, dass Sie JSONEncoder besitzen und es verwenden:

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)

Es ist auch möglich, es wie folgt zu verwenden.

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

Pymongo liefert json_util - Sie können diesen verwenden, um BSON-Typen zu behandeln

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

Tatsächliches Beispiel aus json_util .

Anders als bei Flask's jsonify wird bei "dumps" eine Zeichenfolge zurückgegeben, sodass sie nicht als 1: 1-Ersatz für Flask's jsonify verwendet werden kann.

Aber diese Frage zeigt, dass wir mit json_util.dumps () serialisieren, mit json.loads () zurück in dict konvertieren und schließlich Flask's jsonify aufrufen können.

Beispiel (abgeleitet von der Antwort der vorherigen Frage):

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

Diese Lösung konvertiert ObjectId und andere (z. B. Binär, Code usw.) in eine entsprechende Zeichenfolge wie "$ oid".

Die JSON-Ausgabe würde folgendermaßen aussehen:

{
  "_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)

Dies ist das Beispiel für die Konvertierung von BSON in ein JSON-Objekt. Sie können dies versuchen.

17
vinit kantrod

Als schnellen Ersatz können Sie {'owner': objectid} In {'owner': str(objectid)} ändern.

Aber das Definieren eines eigenen JSONEncoder ist eine bessere Lösung, es hängt von Ihren Anforderungen ab.

16
MostafaR

So habe ich den Fehler kürzlich behoben

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

Ich weiß, dass ich zu spät poste, dachte aber, dass es zumindest ein paar Leuten helfen würde!

Die beiden von tim und defuz genannten Beispiele (die oben gewählt wurden) funktionieren einwandfrei. Es gibt jedoch einen winzigen Unterschied, der manchmal erheblich sein kann.

  1. Die folgende Methode fügt ein zusätzliches Feld hinzu, das redundant ist und möglicherweise nicht in allen Fällen ideal ist

Pymongo bietet json_util - Sie können dieses verwenden, um BSON-Typen zu behandeln

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

  1. Wobei die JsonEncoder-Klasse die gleiche Ausgabe im String-Format liefert, die wir benötigen, und wir zusätzlich json.loads (output) verwenden müssen. Aber es führt zu

Ausgabe: {"_id": "abc123"}

Obwohl die erste Methode einfach aussieht, ist für beide Methoden nur ein minimaler Aufwand erforderlich.

3
rohithnama

Hier posten, da ich denke, dass es für Leute nützlich sein kann, die Flask mit pymongo verwenden. Dies ist meine aktuelle "Best Practice" -Einstellung, um flask zuzulassen, dass Pymongo-Bson-Datentypen gemarshallt werden.

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

Warum tun Sie dies, anstatt BSON oder mongod extended JSON zu bedienen?

Ich denke, die Bereitstellung von speziellem JSON für Mongo stellt eine Belastung für Clientanwendungen dar. Die meisten Client-Apps kümmern sich nicht um die komplexe Verwendung von Mongo-Objekten. Wenn ich Extended Json bediene, muss ich es jetzt auf der Serverseite und auf der Clientseite verwenden. ObjectId und Timestamp lassen sich leichter als Zeichenfolgen verwenden, und so bleibt der ganze Wahnsinn der Mongos auf dem Server unter Quarantäne.

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

Ich denke, das ist für die meisten Anwendungen weniger umständlich als.

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

Flask's jsonify bietet eine Sicherheitsverbesserung, wie in JSON Security beschrieben. Wenn mit Flask ein benutzerdefinierter Encoder verwendet wird, sollten Sie die in JSON Security beschriebenen Punkte berücksichtigen

2
Anish

Die meisten Benutzer, die den Fehler "Nicht JSON serialisierbar" erhalten, müssen lediglich default=str bei Verwendung von json.dumps . Beispielsweise:

json.dumps(my_obj, default=str)

Dadurch wird eine Konvertierung in str erzwungen, wodurch der Fehler verhindert wird. Schauen Sie sich dann natürlich die generierte Ausgabe an, um zu bestätigen, dass Sie sie benötigen.

2
Acumenus

Wenn Sie die _id der Datensätze nicht benötigen, empfehle ich, sie bei der Abfrage der DB zu deaktivieren, damit Sie die zurückgegebenen Datensätze direkt drucken können, z

Um die _id beim Abfragen zu deaktivieren und dann Daten in einer Schleife auszudrucken, schreiben Sie so etwas

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

LÖSUNG für: Mongoengine + Marshmallow

Wenn Sie mongoengine und marshamallow verwenden, ist diese Lösung möglicherweise für Sie anwendbar.

Grundsätzlich habe ich das String -Feld von Marshmallow importiert und das voreingestellte Schema id Überschrieben, damit es String -codiert ist.

from Marshmallow import Schema
from Marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

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

Ich möchte eine zusätzliche Lösung anbieten, die die akzeptierte Antwort verbessert. Ich habe die Antworten zuvor in einem anderen Thread bereitgestellt hier .

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

in meinem Fall brauchte ich so etwas:

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