You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI单请求中同时上传文件与JSON对象?

FastAPI单次请求同时上传文件和JSON对象报422错误

错误信息

422
{"detail":[{"loc":["body"],"msg":"value is not a valid list","type":"type_error.list"},{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}

客户端代码

import requests

url = "http://localhost:8000/"
file = "..."
data = {...}

files = {"pdf": (file, open(file, "rb"), "application/pdf")}

response = requests.post(url, json=data, files=files)

已尝试手动设置请求头为multipart/form-data和multipart/related,问题依旧。

服务端代码

from fastapi import FastAPI, Body, UploadFile, File
from pydantic import BaseModel

app = FastAPI()

class SomeMetadata(BaseModel):
    # 模型字段定义
    ...

@app.post("/")
def submit(someMetadata: SomeMetadata = Body(...), pdf: UploadFile = File(...)):
        return ...

问题原因

当使用requests.post()同时传入json和files参数时,requests会自动将请求编码为multipart/form-data,但json参数并不会被作为请求体的JSON数据发送,而是会被当作表单中的一个普通字段。而FastAPI的Body(...)期望从请求体的原始JSON中解析数据,两者不匹配,导致类型校验失败,抛出422错误。

解决方案

方法一:将JSON序列化为字符串,作为multipart请求的一个字段传递

修改客户端代码

将JSON数据序列化为字符串,放到files字典中作为一个单独的部分发送:

import requests
import json

url = "http://localhost:8000/"
file = "..."
data = {...}

# 将JSON数据转为字符串
data_str = json.dumps(data)

files = {
    "pdf": (file, open(file, "rb"), "application/pdf"),
    "someMetadata": (None, data_str, "application/json")  # 传递JSON字符串
}

response = requests.post(url, files=files)

服务端代码调整(两种方式可选)

  • 方式A:保持原有定义,FastAPI会自动解析multipart中的JSON字段
@app.post("/")
def submit(someMetadata: SomeMetadata = Body(...), pdf: UploadFile = File(...)):
        return {"metadata": someMetadata, "filename": pdf.filename}
  • 方式B:显式接收字符串后解析(更直观)
from fastapi import Form

@app.post("/")
def submit(someMetadata_str: str = Form(...), pdf: UploadFile = File(...)):
    someMetadata = SomeMetadata.parse_raw(someMetadata_str)
    return {"metadata": someMetadata, "filename": pdf.filename}

方法二:使用Pydantic模型结合Form依赖解析

如果希望更贴合表单提交的模式,可以自定义依赖项来解析表单中的JSON字符串:

from fastapi import Depends, Form
from pydantic import BaseModel

def get_metadata(someMetadata: str = Form(...)) -> SomeMetadata:
    return SomeMetadata.parse_raw(someMetadata)

@app.post("/")
def submit(someMetadata: SomeMetadata = Depends(get_metadata), pdf: UploadFile = File(...)):
        return {"metadata": someMetadata, "filename": pdf.filename}

注意事项

  • 不要同时使用requests.post()的json和files参数,两者会冲突,导致请求编码不符合预期。
  • 当发送multipart/form-data请求时,所有数据(包括JSON)都需要作为表单字段或请求部分传递,而非原始JSON请求体。

内容的提问来源于stack exchange,提问作者Anm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 22:17:22