skip to Main Content

Assume a dictionary with an arbitrary structure, where some values are native Python objects and others are instances of PyDantic’s BaseModel subclasses

e.g.

my_dict = {"key1": "value1",
           "key2": {"key3": pydantic_object1, 
                    "key4": 4},
           "key5": pydantic_object2}

I want to dump this dictionary to a JSON file. The naive solution of using BaseModel.model_dump() to convert first all the PyDantic objects to dictionaries and then using json.dump doesn’t work, because some of the attributes of the PyDantic objects cannot be serialized by the native serializer, e.g. datetimess and other custom objects which their serializers are attached to the object’s implementation.

I also couldn’t figure out how to write a custom encoder that will user PyDantic’s built in JSON encoder.

How would you solve this (PyDantic v2 and above)

2

Answers


  1. Only Using Pydantic V2

    Use model_dump_json() instead of model_dump(). I assume SomePydanticModel is pydantic model.

    my_dict = SomePydanticModel.model_dump_json()
    

    Ref: https://docs.pydantic.dev/latest/concepts/serialization/#modelmodel_dump_json

    Using with FastAPI

    If you are using pydantic with FastAPI, use jsonable_encoder

    from datetime import datetime
    
    from fastapi import FastAPI
    from fastapi.encoders import jsonable_encoder
    from pydantic import BaseModel
    
    fake_db = {}
    
    
    class Item(BaseModel):
        title: str
        timestamp: datetime
        description: str | None = None
    
    
    app = FastAPI()
    
    
    @app.put("/items/{id}")
    def update_item(id: str, item: Item):
        json_compatible_item_data = jsonable_encoder(item)
        fake_db[id] = json_compatible_item_data
    

    The above code is in JSON Compatible Encoder

    Login or Signup to reply.
  2. My Answer

    You can use json.dumps(obj, default=serialize_function_if_raise>) to handle the serialization.

    My Example

    from pydantic import BaseModel, VERSION
    import json
    
    print(VERSION)
    # > 2.4.2
    
    
    class Model1(BaseModel):
        i: int = 1
    
        def model_dump(self):  # customize
            d = super().model_dump()
            for k, v in d.items():
                d[k] = f'{v} !'  # < example: add a `!`
            return d  # return a dict
    
    
    class Model2(BaseModel):
        model1: Model1 = Model1()
    
    
    pydantic_object1 = Model1()
    pydantic_object2 = Model2()
    my_dict = {
        "key1": "value1",
        "key2": {"key3": pydantic_object1, "key4": 4},
        "key5": pydantic_object2,
    }
    
    
    print(json.dumps(my_dict, default=lambda x: x.model_dump()))
    # > {"key1": "value1", "key2": {"key3": {"i": "1 !"}, "key4": 4}, "key5": {"model1": {"i": 1}}}
    

    Follow Up

    import json
    class ComplexEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, complex):
                return [obj.real, obj.imag]
            # Let the base class default method raise the TypeError
            return json.JSONEncoder.default(self, obj)
    
    json.dumps(2 + 1j, cls=ComplexEncoder)
    
    ComplexEncoder().encode(2 + 1j)
    
    list(ComplexEncoder().iterencode(2 + 1j))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search