Naxi
Naxi

Reputation: 2016

How to resolve pydantic model is not JSON serializable

I am having below pydantic models.

class SubModel(BaseModel):
    columns: Mapping
    key: List[str]
    required: Optional[List[str]]

    class Config:
        anystr_strip_whitespace: True
        extra: Extra.allow
        allow_population_by_field_name: True


class MyModel(BaseModel):
    name: str
    config1: Optional[SubModel]
    config2: Optional[Mapping]
    class Config:
        anystr_strip_whitespace: True
        extra: Extra.allow
        allow_population_by_field_name: True

When I am trying to do a dumps on this, I am getting model is not JSON serializable

from io import BytesIO
from orjson import dumps
    
bucket = s3.Bucket(bucket_name)
bucket.upload(BytesIO(dumps(data)), key, ExtraArgs={'ContentType': 'application/json'})

Error -

TypeError: Type is not JSON serializable: MyModel

data is a normal python dictionary with one of item of type MyModel. Tried to use .json() but get dict has no attribute json.

I am stuck here. Can someone help me.

Upvotes: 30

Views: 60091

Answers (7)

Joe
Joe

Reputation: 7121

With Pydantic v2 and FastAPI / Starlette you can create a less picky JSONResponse using Pydantic's model.model_dump_json(...) (doc) by overriding JSONResponse.render() (starlette doc)

Pydantic can serialize many commonly used types to JSON that would otherwise be incompatible with a simple json.dumps(foobar) (e.g. datetime, date or UUID).

Also NaN, btw.

from fastapi.responses import JSONResponse
from pydantic import BaseModel

class JSONResponsePydanticV2(JSONResponse):
    """JSON response for Pydantic models."""

    def render(self, content: BaseModel) -> bytes:
        """Render JSON response."""
        if content is None:
            return b""

        if isinstance(content, list) and len(content) == 0:
            return b"[]"

        if isinstance(content, dict) and len(content) == 0:
            return b"{}"

        return content.model_dump_json(exclude_unset=True, exclude_none=True).encode(
            "utf-8"
        )

I think this is basically something like FastAPI's jsonable_encoder() plus json.dumps().

Upvotes: 1

vahid sabet
vahid sabet

Reputation: 583

Use the following instructions:

from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

return JSONResponse(jsonable_encoder(pydantic models))

Upvotes: 0

Taraskin
Taraskin

Reputation: 792

Got similar issue for FastAPI response, solved by:

return JSONResponse(content=jsonable_encoder(item), status_code=200)

or can be just like this:

return jsonable_encoder(item)

where jsonable_encoder is:

from fastapi.encoders import jsonable_encoder

More details are here: https://fastapi.tiangolo.com/tutorial/encoder/

Upvotes: 27

Vlad Stremousov
Vlad Stremousov

Reputation: 39

In modern Pydantic, there is a method json()

b = Book(name="King")
return b.json()

In general, I could recommend to run dir(b), and then you can see all the methods, properties, fields, and so on

Upvotes: 3

Vernadsky
Vernadsky

Reputation: 91

From my perspective, the best way is to use built-in Pydantic model method model_dump_json()

Like:

b = Book(name="King")
return web.json_response(b.model_dump_json())

Upvotes: 8

miksus
miksus

Reputation: 3417

What about just setting a default encoder (which is used if all else fails)?

orjson.dumps(
    MyModel(name="asd"),
    default=lambda x: x.dict()
)
# Output: b'{"name":"asd","config1":null,"config2":null}'

Or with more nested:

orjson.dumps(
    {"a_key": MyModel(name="asd")}, 
    default=lambda x: x.dict()
)
# Output: b'{"a_key":{"name":"asd","config1":null,"config2":null}}'

If you have other types than Pydantic, just make a function and handle all types you have separately.

It also works with json library from standard library (for those that don't use orjson).

Upvotes: 2

Josep Pascual
Josep Pascual

Reputation: 186

Here the problem is that pydantic models are not json serializable by default, in your case, you can call data.dict() to serialize a dict version of your model.

from io import BytesIO
from orjson import dumps

bucket = s3.Bucket(bucket_name)
bucket.upload(BytesIO(dumps(data.dict())), key, ExtraArgs={'ContentType': 'application/json'})

Upvotes: 1

Related Questions