TypeError: ObjectId ('') không thể tuần tự hóa JSON


109

Phản hồi của tôi từ MongoDB sau khi truy vấn một hàm tổng hợp trên tài liệu bằng Python, Nó trả về phản hồi hợp lệ và tôi có thể in nó nhưng không thể trả lại.

Lỗi:

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

In:

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

Nhưng khi tôi cố gắng quay lại:

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

Đó là cuộc gọi 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 được kết nối tốt và bộ sưu tập cũng ở đó và tôi đã nhận lại kết quả mong đợi hợp lệ nhưng khi tôi cố gắng trả lại nó cho tôi lỗi Json. Bất kỳ ý tưởng nào về cách chuyển đổi phản hồi trở lại thành JSON. Cảm ơn

Câu trả lời:


118

Bạn nên xác định mình sở hữu JSONEncodervà sử dụng nó:

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)

Cũng có thể sử dụng nó theo cách sau.

json.encode(analytics, cls=JSONEncoder)

Hoàn hảo! Nó đã làm việc cho tôi. Tôi đã có một lớp mã hóa Json, Làm cách nào để tôi có thể hợp nhất lớp đó với lớp của bạn? Lớp mã hóa Json đã có của tôi là: 'class MyJsonEncoder (json.JSONEncoder): def default (self, obj): if isinstance (obj, datetime): return str (obj.strftime ("% Y-% m-% d% H:% M:% S")) trả về json.JSONEncoder.default (self, obj) '
Irfan

1
@IrfanDayan, chỉ cần thêm if isinstance(o, ObjectId): return str(o)trước returntrong phương thức default.
defuz

2
Bạn có thể thêm from bson import ObjectIdđể mọi người có thể sao chép-dán nhanh hơn không? Cảm ơn!
Liviu Chircu

@defuz Tại sao không chỉ sử dụng str? Có gì sai với cách tiếp cận đó?
Kevin,

@defuz: Khi tôi cố gắng sử dụng điều này, ObjectID bị xóa, nhưng phản hồi json của tôi bị chia thành các ký tự đơn. Ý tôi là khi tôi in từng phần tử từ json kết quả trong vòng lặp for, tôi nhận được mỗi ký tự như một phần tử. Bất kỳ ý tưởng làm thế nào để giải quyết điều này?
Varij Kapil

119

Pymongo cung cấp json_util - bạn có thể sử dụng json_util đó để xử lý các loại BSON


Tôi đồng ý với @tim, đây là cách chính xác để xử lý dữ liệu BSON đến từ mongo. api.mongodb.org/python/current/api/bson/json_util.html
Joshua Powell

Vâng, có vẻ là nhiều hơn một rắc rối miễn phí nếu chúng tôi sử dụng cách này
jonprasetyo

Đó thực sự là cách tốt nhất.
Rahul

14
Một ví dụ ở đây sẽ là một ít hữu ích hơn, vì đây là cách tốt nhất nhưng các tài liệu liên quan không phải là người sử dụng nhất thân thiện cho noobs
Jake

2
from bson import json_util json.loads(json_util.dumps(user_collection)) ^ điều này hoạt động sau khi cài đặt python-bsonjs vớipipenv 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"}}]'

Ví dụ thực tế từ json_util .

Không giống như jsonify của Flask, "kết xuất" sẽ trả về một chuỗi, vì vậy nó không thể được sử dụng để thay thế 1: 1 cho jsonify của Flask.

Nhưng câu hỏi này cho thấy rằng chúng ta có thể tuần tự hóa bằng cách sử dụng json_util.dumps (), chuyển đổi lại thành dict bằng cách sử dụng json.loads () và cuối cùng gọi jsonify của Flask trên đó.

Ví dụ (lấy từ câu trả lời của câu hỏi trước):

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

Giải pháp này sẽ chuyển đổi ObjectId và những thứ khác (tức là Binary, Code, v.v.) thành một chuỗi tương đương như "$ oid."

Đầu ra JSON sẽ giống như sau:

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

Chỉ cần làm rõ, không cần gọi 'jsonify' trực tiếp từ trình xử lý yêu cầu Flask - chỉ cần trả lại kết quả đã được khử trùng.
oferei

Bạn hoàn toàn đúng. Một lệnh Python (mà json.loads trả về) sẽ tự động được Flask hợp nhất.
Garren S

Không phải là một đối tượng dict không thể gọi?
SouvikMaji

@ rick112358 làm thế nào để một chính tả không thể gọi được liên quan đến Câu hỏi & Đáp này?
Garren S,

bạn cũng có thể sử dụng json_util.loads () để lấy lại cùng một từ điển (thay vì một từ điển có khóa '$ 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)

Đây là ví dụ mẫu để chuyển đổi BSON thành đối tượng JSON. Bạn có thể thử điều này.


21

Hầu hết người dùng nhận được lỗi "không thể tuần tự hóa JSON" chỉ cần chỉ định default=strkhi sử dụng json.dumps. Ví dụ:

json.dumps(my_obj, default=str)

Điều này sẽ buộc chuyển đổi thành str, ngăn ngừa lỗi. Tất nhiên sau đó nhìn vào đầu ra được tạo để xác nhận rằng đó là thứ bạn cần.


16

Để thay thế nhanh chóng, bạn có thể thay đổi {'owner': objectid}thành{'owner': str(objectid)} .

Nhưng xác định của riêng bạn JSONEncoderlà một giải pháp tốt hơn, nó phụ thuộc vào yêu cầu của bạn.


6

Đăng ở đây vì tôi nghĩ nó có thể hữu ích cho những người sử dụng Flaskvới pymongo. Đây là thiết lập "phương pháp hay nhất" hiện tại của tôi để cho phép bình sắp xếp các kiểu dữ liệu 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

Tại sao lại làm điều này thay vì phân phát BSON hoặc JSON mở rộng mongod ?

Tôi nghĩ rằng việc phục vụ JSON đặc biệt của mongo đặt ra gánh nặng cho các ứng dụng khách. Hầu hết các ứng dụng khách sẽ không quan tâm đến việc sử dụng các đối tượng mongo theo bất kỳ cách phức tạp nào. Nếu tôi phục vụ json mở rộng, bây giờ tôi phải sử dụng nó phía máy chủ và phía máy khách. ObjectIdTimestampdễ làm việc hơn với dạng chuỗi và điều này giữ cho tất cả những điều điên rồ về điều phối mongo này được cách ly với máy chủ.

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

Tôi nghĩ rằng điều này ít rắc rối hơn để làm việc với hầu hết các ứng dụng hơn.

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

4

Đây là cách gần đây tôi đã sửa lỗi

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

trong trường hợp này bạn không đi qua '_id' thuộc tính, thay vì chỉ xóa '_id' và thông qua các thuộc tính khác của doc
Muhriddin Ismoilov

3

Tôi biết tôi đăng bài muộn nhưng nghĩ rằng nó sẽ giúp ích cho ít nhất một vài người!

Cả hai ví dụ được đề cập bởi tim và defuz (được bình chọn nhiều nhất) đều hoạt động hoàn hảo. Tuy nhiên, có một sự khác biệt về phút mà đôi khi có thể là đáng kể.

  1. Phương pháp sau thêm một trường bổ sung là thừa và có thể không lý tưởng trong mọi trường hợp

Pymongo cung cấp json_util - bạn có thể sử dụng json_util đó để xử lý các loại BSON

Đầu ra: {"_id": {"$ oid": "abc123"}}

  1. Trong đó lớp JsonEncoder cung cấp cùng một đầu ra ở định dạng chuỗi khi chúng ta cần và chúng ta cần sử dụng thêm json.loads (đầu ra). Nhưng nó dẫn đến

Đầu ra: {"_id": "abc123"}

Mặc dù, phương pháp đầu tiên trông đơn giản, nhưng cả hai phương pháp này đều cần nỗ lực rất ít.


này là rất hữu ích cho các pytest-mongodbPlugin khi tạo đồ đạc
tsveti_iko

3

trong trường hợp của tôi, tôi cần một cái gì đó như thế này:

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

1
+1 Hà! Nó có thể được đơn giản hơn 😍 Nói chung; để tránh tất cả những rắc rối với bộ mã hóa tùy chỉnh và nhập bson, hãy truyền ObjectID thành chuỗi :object['_id'] = str(object['_id'])
Vexy

2

Jsonify của Flask cung cấp tăng cường bảo mật như được mô tả trong JSON Security . Nếu bộ mã hóa tùy chỉnh được sử dụng với Flask, tốt hơn hết hãy xem xét các điểm được thảo luận trong Bảo mật JSON


2

Tôi muốn cung cấp một giải pháp bổ sung để cải thiện câu trả lời được chấp nhận. Trước đây tôi đã cung cấp câu trả lời trong một chủ đề khác ở đây .

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

Nếu bạn không cần _id của các bản ghi, tôi khuyên bạn nên bỏ đặt nó khi truy vấn DB điều này sẽ cho phép bạn in trực tiếp các bản ghi trả về, ví dụ:

Để bỏ đặt _id khi truy vấn và sau đó in dữ liệu trong một vòng lặp, bạn viết một cái gì đó như thế này

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

0

GIẢI PHÁP cho: mongoengine + marshmallow

Nếu bạn sử dụng mongoenginemarshamallowthì giải pháp này có thể áp dụng cho bạn.

Về cơ bản, tôi đã nhập Stringtrường từ marshmallow và tôi đã ghi đè lên mặc định Schema idđược Stringmã hóa.

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

Nếu bạn không muốn _idphản hồi, bạn có thể cấu trúc lại mã của mình như sau:

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

Điều này sẽ loại bỏ TypeError: ObjectId('') is not JSON serializablelỗi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.