Go 的 http.Request 不支持自动序列化绑定,需手动读取并解析请求体;JSON 用 io.ReadAll + json.Unmarshal,表单用 r.ParseForm + r.FormValue,查询参数用 r.URL.Query()。

Go 的 http.Request 本身不支持自动序列化绑定,必须手动解析
Go 标准库的 http.Request 没有内置类似 Python Flask 的 request.json() 或 Node.js Express 的 body-parser 中间件。所有请求体(JSON、form、query)都需显式读取、解码、校验——这是设计选择,不是遗漏。
常见错误现象:直接对 r.Body 多次调用 json.NewDecoder(r.Body).Decode() 导致后续读取返回空或 io.EOF,因为 Body 是单次读取流。
- 务必在绑定前用
io.ReadAll(r.Body)一次性读取,再用bytes.NewReader()重建可重用的io.ReadCloser - 或使用
r.ParseForm()+r.FormValue()处理application/x-www-form-urlencoded - 对于
application/json,优先用json.Unmarshal()解析原始字节,而非反复操作r.Body
用 json.Unmarshal() 绑定 JSON 请求体最稳妥
这是最常用也最可控的方式。关键点在于:先读全,再解码,最后把数据映射到结构体字段。
示例场景:接收 POST /users,请求体为 JSON:
{
"name": "Alice",
"age": 30
}
对应结构体和处理逻辑:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func createUser(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
defer r.Body.Close() // 注意:r.Body 已被读完,但 Close 仍应调用
var u User
if err := json.Unmarshal(body, &u); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
// u.Name, u.Age 现在已填充
}
-
json:tag 控制字段映射,缺失 tag 时默认按字段名匹配(首字母大写才导出) - 如果字段类型不匹配(如传字符串给
int),json.Unmarshal()直接报错,不会静默忽略 - 不要用
json.NewDecoder(r.Body).Decode(&u)后再读其他内容——r.Body已耗尽
Query 和 Form 参数要用 r.URL.Query() 或 r.ParseForm()
GET 查询参数和 POST 表单提交(application/x-www-form-urlencoded)不能用 json.Unmarshal(),得走不同路径。
- Query 参数:用
r.URL.Query().Get("key")或r.URL.Query()["key"]获取字符串切片 - Form 参数:先调
r.ParseForm()(它会自动区分GETquery 和POSTbody),再用r.FormValue("key") -
r.ParseForm()不解析application/json,只处理表单编码;若混合使用,需自行判断Content-Type - 注意:
r.FormValue()总是返回字符串,数字/布尔需手动转换(如strconv.Atoi())
第三方库如 gin 或 echo 提供了“自动绑定”,但本质仍是封装上述逻辑
比如 gin.Context.ShouldBindJSON() 内部就是先 io.ReadAll() 再 json.Unmarshal();ShouldBind() 会根据 Content-Type 自动选 JSON/form/query 分支。它们没跳过底层约束,只是帮你少写几行。
- 依赖库会增加二进制体积和学习成本,纯标准库项目没必要引入
- 自定义绑定逻辑(如带默认值、字段级校验)在标准库中反而更清晰——没有魔法,错在哪一眼可见
- 真正容易被忽略的是:所有绑定都发生在 handler 函数内,无法像中间件那样全局拦截并修改
r.Body流(除非用 wrapper 包一层http.Handler)

















