← oracle_refix

fastapi_14616

resolved RESOLVED UNSUBMITTED PASS · None tool calls · 0 s · fastapi/fastapi

Task input

🐛 Fix using `Json[list[str]]` type (issue #10997)

Fixes Form() not working with Pydantic `Json[T]` types when T is a sequence type (list, set, tuple).

Closes #10997

When using `Form()` with `Json[list[str]]`, FastAPI incorrectly treated it as a sequence field and used `getlist()`, wrapping the JSON string in a list. This caused Pydantic to fail parsing with:
```
TypeError: JSON input should be string, bytes or bytearray
```

**Before fix:**
```python
# Client sends
data = {"items": '["abc", "def"]'}  # JSON string

# FastAPI incorrectly does
value = values.getlist("items")  # ['["abc", "def"]']  ❌
```

**After fix:**
```python
# FastAPI correctly does
value = values.get("items")  # '["abc", "def"]'  ✅
```

This fix enables legitimate use cases like file uploads with JSON metadata:

```python
@app.post("/upload")
def upload(
    file: UploadFile,
    tags: Annotated[Json[list[str]], Form()]
):
    return {"file": file.filename, "tags": tags}
```

Pydantic v1 support for `Json[T]` with `Form()` is not included in this PR because `metadata` attribute does not exist in Pydantic v1's `FieldInfo`.

## Related
- Issue #10997

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -51,7 +51,7 @@
 from fastapi.security.oauth2 import SecurityScopes
 from fastapi.types import DependencyCacheKey
 from fastapi.utils import create_model_field, get_path_param_names
-from pydantic import BaseModel
+from pydantic import BaseModel, Json
 from pydantic.fields import FieldInfo
 from starlette.background import BackgroundTasks as StarletteBackgroundTasks
 from starlette.concurrency import run_in_threadpool
@@ -726,11 +726,19 @@ def _validate_value_with_model_field(
         return v_, []
 
 
+def _is_json_field(field: ModelField) -> bool:
+    return any(type(item) is Json for item in field.field_info.metadata)
+
+
 def _get_multidict_value(
     field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None
 ) -> Any:
     alias = alias or get_validation_alias(field)
-    if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
+    if (
+        (not _is_json_field(field))
+        and is_sequence_field(field)
+        and isinstance(values, (ImmutableMultiDict, Headers))
+    ):
         value = values.getlist(alias)
     else:
         value = values.get(alias, None)

Test output

show
....                                                                     [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
4 passed, 1 warning in 0.56s