TypeError: ObjectId ('') JSON serileştirilebilir değil


111

Python kullanarak belge üzerinde toplanmış bir işlevi sorguladıktan sonra MongoDB'den yanıtım, geçerli yanıt döndürüyor ve yazdırabiliyorum ama geri döndüremiyorum.

Hata:

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

Yazdır:

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

Ama geri dönmeye çalıştığımda:

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

RESTfull çağrı:

@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 iyi bağlandı ve koleksiyon da var ve geçerli beklenen sonucu geri aldım ancak geri dönmeye çalıştığımda bana Json hatası veriyor. Yanıtı JSON'a nasıl dönüştürebileceğiniz hakkında herhangi bir fikir. Teşekkürler

Yanıtlar:


120

Kendinizi JSONEncoderve onu kullanarak tanımlamalısınız :

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)

Aşağıdaki şekilde kullanmak da mümkündür.

json.encode(analytics, cls=JSONEncoder)

Mükemmel! Benim için çalıştı. Zaten bir Json kodlayıcı sınıfım var, bunu sizin sınıfınızla nasıl birleştirebilirim? Zaten Json kodlama sınıfım: 'class MyJsonEncoder (json.JSONEncoder): def default (self, obj): if isinstance (obj, datetime): return str (obj.strftime ("% Y-% m-% d% H:% M:% S")) return json.JSONEncoder.default (self, obj) '
Irfan

1
@IrfanDayan, yönteme if isinstance(o, ObjectId): return str(o)önce ekleyin . returndefault
defuz

2
from bson import ObjectIdHerkesin daha hızlı kopyalayıp yapıştırabilmesi için ekleyebilir misiniz ? Teşekkürler!
Liviu Chircu

@defuz Neden sadece kullanmıyorsunuz str? Bu yaklaşımın nesi yanlış?
Kevin

@defuz: Bunu kullanmaya çalıştığımda ObjectID kaldırılıyor, ancak json cevabım tek karakterlere bölünüyor. Demek istediğim, sonuçta ortaya çıkan json'dan her bir öğeyi bir for döngüsünde yazdırdığımda, her karakteri bir öğe olarak alıyorum. bunu nasıl çözeceğimize dair bir fikri olan?
Varij Kapil

120

Pymongo , json_util sağlar - bunun yerine BSON türlerini işlemek için bunu kullanabilirsiniz


@Tim'e katılıyorum, bu mongo'dan gelen BSON verileriyle başa çıkmanın doğru yolu. api.mongodb.org/python/current/api/bson/json_util.html
Joshua Powell

Evet, bu şekilde kullanırsak daha zahmetsiz gibi görünüyor
jonprasetyo

Aslında en iyi yol bu.
Rahul

14
Buradaki bir örnek biraz daha yararlı olabilir, çünkü bu en iyi yoldur, ancak bağlantılı dokümantasyon çaylaklar için en kullanıcı dostu değildir
Jake

2
from bson import json_util json.loads(json_util.dumps(user_collection)) ^ bu, python-bsonj'leri yükledikten sonra çalıştıpipenv install python-bsonjs
NBhat

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

Json_util'den gerçek örnek .

Flask'ın jsonify'ından farklı olarak, "dökümler" bir dize döndürecektir, bu nedenle Flask'ın jsonify'ının 1: 1 değişimi olarak kullanılamaz.

Ancak bu soru , json_util.dumps () kullanarak serileştirebileceğimizi, json.loads () kullanarak tekrar dict'e dönüştürebileceğimizi ve sonunda Flask'ın jsonify'ını çağırabileceğimizi gösteriyor.

Örnek (önceki sorunun cevabından türetilmiştir):

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

Bu çözüm, ObjectId ve diğerlerini (yani İkili, Kod, vb.) "$ Oid" gibi bir dize eşdeğerine dönüştürecektir.

JSON çıktısı şöyle görünecektir:

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

Sadece açıklığa kavuşturmak için, doğrudan bir Flask istek işleyicisinden 'jsonify'ı çağırmaya gerek yok - sadece sterilize edilmiş sonucu geri gönderin.
oferei

Kesinlikle haklısın. Bir Python diktesi (json.loads döndürür) Flask tarafından otomatik olarak jsonifiye edilmelidir.
Garren S

Bir dikt nesnesi çağrılabilir değil mi?
SouvikMaji

@ rick112358 çağrılabilir olmama emri bu Soru-Cevap ile nasıl ilişkilidir?
Garren S

aynı sözlüğü geri almak için json_util.loads () 'u da kullanabilirsiniz (' $ oid 'anahtarlı bir sözlüğü yerine).
rGun

22

"JSON serileştirilebilir değil" hatasını alan çoğu kullanıcının default=str, kullanırken bunu belirtmesi yeterlidir json.dumps. Örneğin:

json.dumps(my_obj, default=str)

Bu str, hatayı önleyerek bir dönüşüme zorlar . Elbette, ihtiyacınız olan şeyin bu olduğunu doğrulamak için üretilen çıktıya bakın.


21
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)

Bu, BSON'u JSON nesnesine dönüştürmek için örnek örnektir. Bunu deneyebilirsin.


16

Hızlı bir değişim olarak, değiştirebilir {'owner': objectid}için {'owner': str(objectid)}.

Ancak kendi kimliğinizi tanımlamak JSONEncoderdaha iyi bir çözümdür, gereksinimlerinize bağlıdır.


6

Bunun kullanan kişiler için yararlı olabilir düşünüyorum burada Gönderme Flaskile pymongo. Bu, flask'ın pymongo bson veri türlerini sıralaması için şu anki "en iyi uygulama" kurulumumdur.

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

Bunu BSON veya mongod genişletilmiş JSON sunmak yerine neden yapalım ?

Mongo özel JSON hizmetinin istemci uygulamalarına bir yük getirdiğini düşünüyorum. Çoğu istemci uygulaması, mongo nesnelerini karmaşık bir şekilde kullanmayı önemsemeyecektir. Eğer genişletilmiş json servis edersem, şimdi onu sunucu tarafı ve istemci tarafı kullanmalıyım. ObjectIdve Timestampdizeler olarak çalışmak daha kolaydır ve bu, tüm bu mongo marşallama çılgınlığını sunucuda karantinaya alır.

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

Bunun çoğu uygulamada çalışmaktan daha az zahmetli olduğunu düşünüyorum .

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

4

Son zamanlarda hatayı bu şekilde düzelttim

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

bu durumda '_id' özniteliğini
iletmiyorsunuz

3

Geç yayınladığımı biliyorum ama en azından birkaç kişiye yardımcı olacağını düşündüm!

Tim ve defuz (en çok oylananlar) tarafından bahsedilen örneklerin ikisi de mükemmel şekilde çalışıyor. Ancak, bazen önemli olabilecek bir dakika farkı vardır.

  1. Aşağıdaki yöntem, gereksiz olan ve her durumda ideal olmayabilecek fazladan bir alan ekler

Pymongo, json_util sağlar - bunun yerine BSON türlerini işlemek için bunu kullanabilirsiniz

Çıktı: {"_id": {"$ oid": "abc123"}}

  1. JsonEncoder sınıfı ihtiyacımız olan aynı çıktıyı string formatında verir ve ek olarak json.loads (output) kullanmamız gerekir. Ama yol açar

Çıktı: {"_id": "abc123"}

İlk yöntem basit görünse de, her iki yöntem de çok az çaba gerektirir.


bu, pytest-mongodbfikstür oluştururken eklenti için çok kullanışlıdır
tsveti_iko

3

benim durumumda böyle bir şeye ihtiyacım vardı:

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

1
+1 Ha! Daha basit olabilir miydi? Genel olarak konuşursak; özel kodlayıcılar ve bson içe aktarmayla tüm bulanıklıkları önlemek için ObjectID'yi dizeyeobject['_id'] = str(object['_id'])
çevirin


2

Kabul edilen yanıtı iyileştiren ek bir çözüm sunmak istiyorum. Cevapları daha önce burada başka bir başlıkta vermiştim .

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 __name__ == "__main__":
    application.run()

1

Kayıtların _id'sine ihtiyacınız olmayacaksa, DB'yi sorgularken, döndürülen kayıtları doğrudan yazdırmanıza olanak verecek şekilde ayarını kaldırmanızı tavsiye ederim.

Sorgulama sırasında _id değerini ayarlamak ve ardından verileri bir döngüde yazdırmak için böyle bir şey yazarsınız

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

0

ÇÖZÜM: mongoengine + hatmi

Kullanırsanız mongoengineve marshamallowsonra bu çözüm sizin için geçerli olabilir.

Temelde, ithal Stringhatmi gelen alanını ve ben varsayılan üzerine Schema idolması Stringkodlanmış.

from marshmallow import Schema
from marshmallow.fields import String

class FrontendUserSchema(Schema):

    id = String()

    class Meta:
        fields = ("id", "email")

0
from bson.objectid import ObjectId
from core.services.db_connection import DbConnectionService

class DbExecutionService:
     def __init__(self):
        self.db = DbConnectionService()

     def list(self, collection, search):
        session = self.db.create_connection(collection)
        return list(map(lambda row: {i: str(row[i]) if isinstance(row[i], ObjectId) else row[i] for i in row}, session.find(search))

0

_idYanıt istemiyorsanız , kodunuzu şunun gibi yeniden düzenleyebilirsiniz:

jsonResponse = getResponse(mock_data)
del jsonResponse['_id'] # removes '_id' from the final response
return jsonResponse

Bu, TypeError: ObjectId('') is not JSON serializablehatayı ortadan kaldıracaktır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.