Search code examples
pythonmongodbpymongobson

Pydantic - pass JSON_OPTIONS to custom encoder


Say we have a Pydantic object that we want to insert into MongoDB:

from bson import ObjectId
from datetime import datetime

class A(pydantic.BaseModel):
    w: ObjectId
    x: datetime

The built-in BaseModel.json method does not handle BSON documents, so I need to pass a custom encoder:

from bson import json_util

a = A(w=ObjectId("123"), x=datetime(2023,1,1))
a.json(encoder=json_util.dumps, exclude_none=True)

Because of default behavior for json_util.dumps, above results in a very ugly BSON where dates are shown as either a dictionary ({"$date": "2023-01-01"}) or a timestamp ({"$date": {"$numberLong": "12314215"}}).

Found this documentation on json_util, and I tried to pass in json_options to pydantic.BaseModel.json... but it does not work.

TypeError: JSONEncoder.__init__() got an unexpected keyword argument 'json_options'

But I just passed in a custom encoder... The kwarg should be passed to json_util.dumps, but clearly that is not the case here.

Alternatively, I could convert the whole thing to just a string. But I wanted to properly encode my document before insert into MongoDB. I don't want my ObjectId to be converted to a string.

The data serializes just fine, but looking at these date values in Mongo is just uncomfortable.

This should not be this difficult... What am I missing?

Alternatively, I could convert the whole thing to just a string. But I wanted to properly encode my document before insert into MongoDB. I don't want my ObjectId to be converted to a string.

The data serializes just fine, but looking at these date values in Mongo is just uncomfortable.


Solution

  • Use pydantic's dict() method to convert your model into a dictionary, then insert that into MongoDB:

    from bson import ObjectId
    from datetime import datetime
    import pydantic
    import pymongo
    
    db = pymongo.MongoClient()['test']
    
    class A(pydantic.BaseModel):
        class Config:
            arbitrary_types_allowed = True
    
        w: ObjectId
        x: datetime
    
    a = A(w=ObjectId(), x=datetime(2023, 1, 1))
    
    db['mycollection'].insert_one(a.dict())
    

    mongosh:

    test> db.mycollection.findOne()
    {
      _id: ObjectId("649627207cac51eab98924cb"),
      w: ObjectId("649627207cac51eab98924ca"),
      x: ISODate("2023-01-01T00:00:00.000Z")
    }