tiangolo/fastapi · error · ValueError

{errors}

Error message

{errors}

What it means

ValueError raised by jsonable_encoder as a last resort (encoders.py:355). After the object matches no registered encoder, is not a pydantic v1 model, and both dict(obj) and vars(obj) raise, FastAPI collects the two caught exceptions into a list and raises ValueError(errors) from the last one. The object is simply not JSON-encodable by FastAPI's rules.

Source

Thrown at fastapi/encoders.py:355

        return ENCODERS_BY_TYPE[type(obj)](obj)
    for encoder, classes_tuple in encoders_by_class_tuples.items():
        if isinstance(obj, classes_tuple):
            return encoder(obj)
    if is_pydantic_v1_model_instance(obj):
        raise PydanticV1NotSupportedError(
            "pydantic.v1 models are no longer supported by FastAPI."
            f" Please update the model {obj!r}."
        )
    try:
        data = dict(obj)
    except Exception as e:
        errors: list[Exception] = []
        errors.append(e)
        try:
            data = vars(obj)
        except Exception as e:
            errors.append(e)
            raise ValueError(errors) from e
    return jsonable_encoder(
        data,
        include=include,
        exclude=exclude,
        by_alias=by_alias,
        exclude_unset=exclude_unset,
        exclude_defaults=exclude_defaults,
        exclude_none=exclude_none,
        custom_encoder=custom_encoder,
        sqlalchemy_safe=sqlalchemy_safe,
    )

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Declare a pydantic response_model so FastAPI serializes the object through the model instead of duck-typing it.
  2. Convert the object to a dict/list of primitives before returning (e.g. [h.model_dump() for h in rows]).
  3. Add a custom_encoder for the specific type, or pass json_encoders via response_model.
  4. Implement asdict()/__iter__ (to make dict(obj) work) or a normal __dict__ on the class.

Example fix

// before
@app.get("/widgets/{i}")
def read(i: int):
    return session.get(Widget, i)  # raw ORM object, dict()/vars() both fail
# -> ValueError([...])

// after
class WidgetOut(BaseModel):
    id: int
    name: str

@app.get("/widgets/{i}", response_model=WidgetOut)
def read(i: int):
    return session.get(Widget, i)
Defensive patterns

Strategy: try-catch

Validate before calling

# Verify an object is jsonable_encoder-friendly before returning it
from fastapi.encoders import jsonable_encoder

def safe(obj):
    try:
        jsonable_encoder(obj)
        return obj
    except Exception:
        # convert ORM/dataclass to dict before returning
        if hasattr(obj, "model_dump"):
            return obj.model_dump()
        if hasattr(obj, "__dict__"):
            return vars(obj)
        raise

Type guard

def is_jsonable(obj) -> bool:
    try:
        jsonable_encoder(obj)
    except Exception:
        return False
    return True

Try / catch

from fastapi.encoders import jsonable_encoder
try:
    payload = jsonable_encoder(result)
except ValueError:
    payload = [r.model_dump() for r in result] if isinstance(result, list) else result.model_dump()

Prevention

When it happens

Trigger: Returning an object from an endpoint that is not a dict/list/primitive/pydantic-model/known type, and that does not support dict() or __dict__ (vars). For example a SQLAlchemy 2.x ORM object without a pydantic response_model, a custom class with __slots__ and no mapping protocol, an open file/regex/lock object, or an unserializable nested value.

Common situations: Returning raw SQLAlchemy/SQLModel ORM rows without a response_model that converts them; a custom dataclass-like object with __slots__; an enum or type object slipping into the response; a relationship/collection that is not JSON-coercible; a non-encodable value (datetime.tzinfo, Decimal without encoder, UUID in a raw object).

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/580d10f3dee5e4b5. Report an issue: GitHub.