]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🐛 Fix using `Json[list[str]]` type (issue #10997) (#14616)
authorKanetsuna Masaya <156692516+mkanetsuna@users.noreply.github.com>
Thu, 5 Feb 2026 18:41:43 +0000 (03:41 +0900)
committerGitHub <noreply@github.com>
Thu, 5 Feb 2026 18:41:43 +0000 (18:41 +0000)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
fastapi/dependencies/utils.py
tests/test_json_type.py [new file with mode: 0644]

index e5ac51d53c8ecfe41b5c3724e91c72087829cd03..b8f7f948c67dc0e8504de7fbb4f10d4702a3355a 100644 (file)
@@ -51,7 +51,7 @@ from fastapi.logger import logger
 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)
diff --git a/tests/test_json_type.py b/tests/test_json_type.py
new file mode 100644 (file)
index 0000000..3e213ea
--- /dev/null
@@ -0,0 +1,63 @@
+import json
+from typing import Annotated
+
+from fastapi import Cookie, FastAPI, Form, Header, Query
+from fastapi.testclient import TestClient
+from pydantic import Json
+
+app = FastAPI()
+
+
+@app.post("/form-json-list")
+def form_json_list(items: Annotated[Json[list[str]], Form()]) -> list[str]:
+    return items
+
+
+@app.get("/query-json-list")
+def query_json_list(items: Annotated[Json[list[str]], Query()]) -> list[str]:
+    return items
+
+
+@app.get("/header-json-list")
+def header_json_list(x_items: Annotated[Json[list[str]], Header()]) -> list[str]:
+    return x_items
+
+
+@app.get("/cookie-json-list")
+def cookie_json_list(items: Annotated[Json[list[str]], Cookie()]) -> list[str]:
+    return items
+
+
+client = TestClient(app)
+
+
+def test_form_json_list():
+    response = client.post(
+        "/form-json-list", data={"items": json.dumps(["abc", "def"])}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == ["abc", "def"]
+
+
+def test_query_json_list():
+    response = client.get(
+        "/query-json-list", params={"items": json.dumps(["abc", "def"])}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == ["abc", "def"]
+
+
+def test_header_json_list():
+    response = client.get(
+        "/header-json-list", headers={"x-items": json.dumps(["abc", "def"])}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == ["abc", "def"]
+
+
+def test_cookie_json_list():
+    client.cookies.set("items", json.dumps(["abc", "def"]))
+    response = client.get("/cookie-json-list")
+    assert response.status_code == 200, response.text
+    assert response.json() == ["abc", "def"]
+    client.cookies.clear()