FastAPI中Query参数需手动归一化才能同时接受字符串和列表:用Union[str, List[str]]注解配合Depends封装解析逻辑,对str调用split(",")并strip,对List[str]直接处理,返回List[str]以确保依赖注入生效。

FastAPI 中 Query 参数如何同时接受字符串和列表
默认情况下,FastAPI 的 Query 会根据类型注解做严格解析:声明为 List[str] 就只认逗号分隔或重复参数;声明为 str 就只收单个值。要两者都兼容,不能靠类型注解自动推导,得手动干预解析逻辑。
用 Union[str, List[str]] + 自定义解析函数
FastAPI 支持 Union 类型,但仅靠它还不够——它会让 OpenAPI 文档混乱,且对单个字符串输入(如 ?tags=python)仍报错,因为 FastAPI 默认把单个值当 str,而你期望它也能进 List 流程。
实操建议:
- 类型注解写成
Union[str, List[str]],并设默认值为None - 在路由函数内部,用
isinstance(value, str)判断是否需要包装成列表:[value] - 如果前端传的是
?tags=a&tags=b,FastAPI 已自动转成["a", "b"];如果是?tags=a,b,需额外用.split(",")处理(但注意空格、编码问题) - 更稳妥的做法是统一用重复参数形式(
?tag=a&tag=b),避免逗号解析歧义
用 Depends 封装兼容逻辑
把判断和归一化逻辑抽成依赖,既复用又保持路由干净。适合多处需要同样行为的场景。
示例:
from fastapi import Depends, Query
from typing import Union, List, Optional
def parse_tags(tag: Union[str, List[str]] = Query(None)) -> List[str]:
if tag is None:
return []
if isinstance(tag, str):
return [t.strip() for t in tag.split(",") if t.strip()]
return [t.strip() for t in tag if t.strip()]
@app.get("/items/")
def list_items(tags: List[str] = Depends(parse_tags)):
return {"tags": tags}
注意点:
- 这个函数返回类型必须是
List[str],否则 FastAPI 不会把它当依赖注入 - 不要在依赖里抛异常(如
HTTPException),FastAPI 会吞掉错误信息;改用raise RequestValidationError或让 FastAPI 自动校验 - OpenAPI 文档中该参数仍显示为
string,因为依赖函数没有类型提示穿透能力
为什么不用 ... = Query(default=[]) 直接设默认空列表
看似简单,但会出问题:当用户完全不传该参数时,确实得到空列表;可一旦传了单个字符串(?tag=go),FastAPI 会尝试把字符串“go”赋给 List[str] 类型变量,直接报 validation error,而不是自动转成 ["go"]。
根本原因:FastAPI 的类型转换器(sequence 转换器)只在明确声明为 List[...] 且请求含多个同名参数时触发,不处理单字符串到单元素列表的隐式转换。
所以绕不过手动归一化——要么在路径函数里写 if isinstance(...),要么用 Depends 封装。没更短的路。










