fix: resolve list[dict] return type producing Root() instead of dicts#3880
Conversation
When a tool returns `list[dict]`, the client deserializes each dict as a `Root()` dataclass with no fields instead of preserving the original dict data. The root cause is in `_get_from_type_handler`: its `"object"` branch always fell through to `_create_dataclass` for schemas without `properties`, creating an empty dataclass named `Root`. The top-level `json_schema_to_type` already handled this case correctly (returning `dict[str, Any]`), but that logic was not shared with `_schema_to_type` which is used when converting nested schemas (e.g., array items). Extract `_object_schema_to_type` to unify the four object-schema cases (dict, typed dict, BaseModel with extra, dataclass) so both top-level and nested paths produce the correct type. Fixes PrefectHQ#3867
jlowin
left a comment
There was a problem hiding this comment.
Thanks for the clean diagnosis and tests on this one — the root cause is exactly right, and the list[dict[str, X]] parametrization plus the end-to-end client test give me a lot of confidence in the fix.
One small note for anyone reading this later: the PR description says the helper unifies top-level and nested paths, but in practice the new _object_schema_to_type is only wired into _get_from_type_handler. The top-level json_schema_to_type (around lines 180-198) and the anyOf dict-in-union branch (around lines 362-374) still carry their own copies of the four-case logic. That's fine — those sites aren't broken, and this is the narrowest fix for the reported bug — but it means the divergence risk that caused #3867 in the first place still exists at two other sites. I'll open a follow-up to route all three through the new helper.
Merging. Thanks again!
Description
Fixes #3867
When a tool returns
list[dict], the client deserializes each dict as aRoot()dataclass with no fields instead of preserving the original dict data:Root cause
_get_from_type_handler's"object"branch always fell through to_create_dataclassfor schemas withoutproperties(like{"type": "object", "additionalProperties": true}— the JSON Schema for baredict). This created an empty dataclass namedRoot.The top-level
json_schema_to_typealready handled this correctly — returningdict[str, Any]— but that logic was not reachable from_schema_to_type, which is used for nested schemas (e.g., array items).Fix
Extract
_object_schema_to_typeto unify the four object-schema cases so both top-level and nested paths produce the correct type:properties+additionalProperties—dict[str, T]properties+ noadditionalProperties—dict[str, Any]properties+additionalProperties is True— Pydantic BaseModelpropertiesonly — dataclassContribution type
Checklist