TypeError : ObjectId ( '')는 JSON 직렬화 가능하지 않습니다.


109

Python을 사용하여 문서에서 집계 함수를 쿼리 한 후 MongoDB에서 내 응답을 반환하면 유효한 응답이 반환되고 인쇄 할 수는 있지만 반환 할 수는 없습니다.

오류:

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

인쇄:

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

하지만 돌아 오려고 할 때 :

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

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가 잘 연결되어 있고 컬렉션도 거기에 있고 유효한 예상 결과를 얻었지만 반환하려고하면 Json 오류가 발생합니다. 응답을 JSON으로 다시 변환하는 방법에 대한 아이디어. 감사

답변:


118

자신이 소유 JSONEncoder하고 사용하는 것을 정의해야 합니다.

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)

다음과 같은 방법으로도 사용할 수 있습니다.

json.encode(analytics, cls=JSONEncoder)

완전한! 그것은 나를 위해 일했습니다. 나는 이미 Json 인코더 클래스를 가지고 있는데, 어떻게 그것을 당신의 클래스와 병합 할 수 있습니까? 이미 Json 인코딩 클래스는 다음과 같습니다. STR (obj.strftime ( "% Y- % M- % D % H %의 M %의 S는")) json.JSONEncoder.default (자기, OBJ) '돌려
이르판

1
@IrfanDayan, 단지 추가 if isinstance(o, ObjectId): return str(o)이전 return방법 default.
defuz 2013 년

2
을 추가 from bson import ObjectId하면 누구나 더 빨리 복사하여 붙여 넣을 수 있습니까? 감사!
Liviu Chircu 2015 년

@defuz 왜 그냥 사용하지 str않습니까? 그 접근 방식에 어떤 문제가 있습니까?
Kevin

@defuz : 이것을 사용하려고하면 ObjectID가 제거되지만 json 응답이 단일 문자로 나뉩니다. for 루프에서 결과 json의 각 요소를 인쇄하면 각 문자를 요소로 얻습니다. 이 문제를 해결하는 방법을 아십니까?
Varij Kapil

119

Pymongo가 제공 json_util - 당신이 핸들 BSON 유형에 대신에 하나를 사용할 수 있습니다


@tim에 동의합니다. 이것은 mongo에서 오는 BSON 데이터를 처리하는 올바른 방법입니다. api.mongodb.org/python/current/api/bson/json_util.html
Joshua Powell

예, 우리는이 방법을 사용하는 경우 무료로 더 번거 로움 것 같다
jonprasetyo

그것이 실제로 가장 좋은 방법입니다.
Rahul

14
이 가장 좋은 방법이지만 링크 된 문서 noobs에 대한 친화적 인 대부분의 사용자가 아닌 같은 예는 여기에 좀 더 도움이 될 것입니다
제이크

2
from bson import json_util json.loads(json_util.dumps(user_collection)) ^ 이것은 python-bsonjs를 설치 한 후에 작동했습니다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의 실제 예 .

Flask의 jsonify와 달리 "dumps"는 문자열을 반환하므로 Flask의 jsonify를 1 : 1로 대체 할 수 없습니다.

그러나이 질문 은 json_util.dumps ()를 사용하여 직렬화하고, json.loads ()를 사용하여 dict로 다시 변환하고 마지막으로 Flask의 jsonify를 호출 할 수 있음을 보여줍니다.

예 (이전 질문의 답변에서 파생 됨) :

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

이 솔루션은 ObjectId 및 기타 (예 : Binary, Code 등)를 "$ oid"와 같은 문자열로 변환합니다.

JSON 출력은 다음과 같습니다.

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

명확히하기 위해 Flask 요청 핸들러에서 직접 'jsonify'를 호출 할 필요가 없습니다. 삭제 된 결과 만 반환하면됩니다.
oferei

당신이 절대적으로 옳습니다. Python dict (json.loads가 반환)는 Flask에 의해 자동으로 jsonified되어야합니다.
Garren S

dict 객체는 호출 할 수 없습니까?
SouvikMaji

@ rick112358 딕셔너리가이 Q & A와 어떤 관련이 있습니까?
Garren S

또한 json_util.loads ()를 사용하여 정확히 동일한 사전을 가져올 수 있습니다 ( '$ oid'키가있는 사전 대신).
rGun

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)

이것은 BSON을 JSON 객체로 변환하는 샘플 예제입니다. 이것을 시도 할 수 있습니다.


21

은 "하지 JSON의 직렬화"오류가 발생 대부분의 사용자는 단순히 지정해야합니다 default=str사용하는 경우 json.dumps. 예를 들면 :

json.dumps(my_obj, default=str)

이렇게하면로 변환 str하여 오류를 방지 할 수 있습니다. 물론 생성 된 출력을보고 그것이 필요한 것인지 확인하십시오.


16

빠른 교체 {'owner': objectid}{'owner': str(objectid)}.

그러나 자신을 정의 JSONEncoder하는 것이 더 나은 솔루션이며 요구 사항에 따라 다릅니다.


6

Flask와 함께 사용 하는 사람들에게 유용 할 것이라고 생각하므로 여기에 게시하십시오 pymongo. 이것은 플라스크가 pymongo 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

BSON 또는 mongod 확장 JSON 을 제공하는 대신 왜 이렇게합니까 ?

mongo 특수 JSON을 제공하면 클라이언트 응용 프로그램에 부담이된다고 생각합니다. 대부분의 클라이언트 앱은 복잡한 방식으로 mongo 객체를 사용하는 데 신경 쓰지 않습니다. 확장 json을 제공하면 이제 서버 측과 클라이언트 측을 사용해야합니다. ObjectIdTimestamp문자열로보다 쉽게 작업하고이 서버에 모든 몽고 마샬링 광기 격리를 유지합니다.

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

나는 이것이 대부분의 응용 프로그램 에서 작업하는 것이 덜 부담 스럽다고 생각합니다 .

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

4

이것이 최근에 오류를 수정 한 방법입니다.

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

이 경우 당신은 대신 삭제 '_id'와 문서의 다른 속성을 통과, '_id'속성을 통과하지 않습니다
Muhriddin Ismoilov

3

나는 내가 늦게 게시한다는 것을 알고 있지만 적어도 몇 사람에게 도움이 될 것이라고 생각했습니다!

tim과 defuz (최우수 투표)가 언급 한 예제 모두 완벽하게 작동합니다. 그러나 때때로 중요한 차이가있을 수 있습니다.

  1. 다음 방법은 중복되고 모든 경우에 이상적이지 않을 수있는 하나의 추가 필드를 추가합니다.

Pymongo는 json_util을 제공합니다-대신 BSON 유형을 처리하는 데 사용할 수 있습니다.

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

  1. JsonEncoder 클래스가 필요한만큼 문자열 형식으로 동일한 출력을 제공하고 json.loads (output)을 추가로 사용해야합니다. 그러나 그것은

출력 : { "_id": "abc123"}

첫 번째 방법은 간단 해 보이지만 두 방법 모두 최소한의 노력 만 필요합니다.


이것은 매우 유용 pytest-mongodb기구를 만들 때 플러그인
tsveti_iko

3

제 경우에는 다음과 같은 것이 필요했습니다.

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

1
+1 하! 더 간단했을까요 😍 일반적으로 말하면; 사용자 지정 인코더 및 bson 가져 오기로 모든 퍼즈를 방지하려면 ObjectID를 문자열로 캐스팅합니다 .object['_id'] = str(object['_id'])
Vexy

2

Flask의 jsonify는 JSON Security에 설명 된대로 보안 강화를 제공합니다 . 사용자 지정 인코더를 Flask와 함께 사용하는 경우 JSON 보안 에서 논의 된 사항을 고려하는 것이 좋습니다.


2

수용된 답변을 개선하는 추가 솔루션을 제공하고 싶습니다. 이전에 여기 에 다른 스레드 에서 답변을 제공했습니다 .

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

레코드의 _id가 필요하지 않은 경우 DB를 쿼리 할 때 설정을 해제하는 것이 좋습니다. 이렇게하면 반환 된 레코드를 직접 인쇄 할 수 있습니다.

쿼리 할 때 _id를 설정 해제 한 다음 루프에서 데이터를 인쇄하려면 다음과 같이 작성합니다.

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

0

솔루션 : mongoengine + marshmallow

당신이 사용하는 경우 mongoenginemarshamallow 다음이 솔루션은 적용 할 수 있습니다.

기본적으로 String마시멜로에서 필드를 가져 왔고 인코딩 Schema id할 기본값 을 덮어 String썼습니다.

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

_id응답을 원하지 않는 경우 다음과 같이 코드를 리팩터링 할 수 있습니다.

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

이렇게하면 TypeError: ObjectId('') is not JSON serializable오류 가 제거됩니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.