pydantic/pydantic · error · ValidationError

{cls.__name__} expected dict not {obj.__class__.__name__}

Error message

{cls.__name__} expected dict not {obj.__class__.__name__}

What it means

Raised by BaseModel.parse_obj (main.py:548) when the passed object is not a dict and cannot be converted via dict(obj) (the conversion raises TypeError or ValueError). parse_obj expects a mapping of field name -> value; non-mapping inputs that are not dict-convertible (e.g. a bare int, str, or a non-iterable object) produce this wrapped ValidationError.

Source

Thrown at pydantic/v1/main.py:548

    def _enforce_dict_if_root(cls, obj: Any) -> Any:
        if cls.__custom_root_type__ and (
            not (isinstance(obj, dict) and obj.keys() == {ROOT_KEY})
            and not (isinstance(obj, BaseModel) and obj.__fields__.keys() == {ROOT_KEY})
            or cls.__fields__[ROOT_KEY].shape in MAPPING_LIKE_SHAPES
        ):
            return {ROOT_KEY: obj}
        else:
            return obj

    @classmethod
    def parse_obj(cls: Type['Model'], obj: Any) -> 'Model':
        obj = cls._enforce_dict_if_root(obj)
        if not isinstance(obj, dict):
            try:
                obj = dict(obj)
            except (TypeError, ValueError) as e:
                exc = TypeError(f'{cls.__name__} expected dict not {obj.__class__.__name__}')
                raise ValidationError([ErrorWrapper(exc, loc=ROOT_KEY)], cls) from e
        return cls(**obj)

    @classmethod
    def parse_raw(
        cls: Type['Model'],
        b: StrBytes,
        *,
        content_type: str = None,
        encoding: str = 'utf8',
        proto: Protocol = None,
        allow_pickle: bool = False,
    ) -> 'Model':
        try:
            obj = load_str_bytes(
                b,
                proto=proto,
                content_type=content_type,
                encoding=encoding,

View on GitHub (pinned to 2e5f0e2b42)

Solutions

  1. Pass a dict (or a dict-convertible mapping) to parse_obj.
  2. For ORM objects use Model.from_orm(obj) (with orm_mode=True).
  3. For scalar/list root values, declare a __root__ field so the value is accepted directly.
  4. Pre-validate the input shape before calling parse_obj.

Example fix

// before
m = M.parse_obj(42)  # raises 'expected dict not int'

// after
m = M.parse_obj({'value': 42})
# or for a scalar root:
class M(BaseModel):
    __root__: int
m = M.parse_obj(42)
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import Mapping

def is_parseable_obj(obj) -> bool:
    return isinstance(obj, Mapping) or _is_dict_convertible(obj)

def _is_dict_convertible(obj) -> bool:
    try:
        dict(obj)
        return True
    except (TypeError, ValueError):
        return False

# usage
if not is_parseable_obj(payload):
    raise TypeError(f'parse_obj expects a dict, got {type(payload).__name__}')
MyModel.parse_obj(payload)

Type guard

from typing import Any, Mapping

def is_dict_like(value: Any) -> bool:
    if isinstance(value, Mapping):
        return True
    try:
        dict(value)
        return True
    except (TypeError, ValueError):
        return False

Try / catch

from pydantic.v1 import ValidationError

try:
    m = MyModel.parse_obj(payload)
except ValidationError as e:
    if 'expected dict not' in str(e):
        m = MyModel.parse_obj({'value': payload})
    else:
        raise

Prevention

When it happens

Trigger: Calling Model.parse_obj(some_int), Model.parse_obj('string'), or parse_obj on a list of non-pair items. For custom-root (__root__) models, _enforce_dict_if_root wraps scalars first, so this fires mainly for non-root models given non-dict input.

Common situations: Passing a JSON-decoded value of the wrong shape, passing an ORM object to parse_obj instead of from_orm, or feeding a scalar where a dict was expected.

Related errors


AI-assisted analysis of pydantic/pydantic@2e5f0e2b42 (2026-08-04). Data as JSON: /data/errors/25e43eac8aa72beb.json. Report an issue: GitHub.