
本文详解 Go 后端接收并反序列化 Python 客户端发送的 JSON POST 请求时的常见错误(如双重序列化导致的 json: cannot unmarshal string into Go value),并提供修复方案、结构体标签规范、错误处理建议及完整可运行示例。
本文详解 go 后端接收并反序列化 python 客户端发送的 json post 请求时的常见错误(如双重序列化导致的 `json: cannot unmarshal string into go value`),并提供修复方案、结构体标签规范、错误处理建议及完整可运行示例。
在 Go Web 开发中,通过 json.Unmarshal 解析 HTTP POST 请求体是基础操作,但极易因客户端序列化方式或结构体标签书写错误而失败。你遇到的错误:
json: cannot unmarshal string into Go value of type main.Game
根本原因在于:Python 客户端对 JSON 数据进行了两次序列化。
? 错误根源分析
你的 Python 代码中:
json_to_send = json.dumps({"RID": "8", "Country": "Australia"})
resp = requests.post(url=addr, json=json_to_send, headers=post_headers)这里存在两个关键问题:
-
requests.post(..., json=...)参数期望接收一个原生 Python 字典(dict),它会自动调用json.dumps()并设置Content-Type: application/json; - 你却先手动
json.dumps()得到一个 JSON 字符串,再传给json=参数 → 导致requests再次将其转为 JSON,最终发送的是类似"{"RID": "8", "Country": "Australia"}"的字符串字面量(即外层被双引号包裹),而非原始 JSON 对象。
Go 服务器收到的请求体实际是:
"{\"RID\": \"8\", \"Country\": \"Australia\"}"这在 JSON 语法中是一个字符串值,而非对象,因此 json.Unmarshal 尝试将该字符串解码为 Game{} 结构体时失败 —— 类型不匹配。
✅ 正确的 Python 客户端写法
import requests
# ✅ 正确:直接传 dict,让 requests 自动处理序列化与 header
data = {"RID": "8", "Country": "Australia"}
resp = requests.post("http://127.0.0.1:8081/wait/", json=data)
print(resp.status_code, resp.text)? 提示:无需手动设置
Content-Type,requests会自动添加application/json。
✅ Go 服务端优化写法(含健壮性增强)
原始代码还存在多个隐患:未检查 ReadAll 错误、defer r.Body.Close() 位置错误(应在读取前 defer)、结构体 JSON 标签格式错误(空格导致失效)、缺少 HTTP 状态响应。以下是修复后的完整服务端:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gorilla/mux"
)
type Game struct {
RID string `json:"RID"` // ❌ 原写法 `json: "RID"` 含空格 → 标签无效!应为 `json:"RID"`
Country string `json:"Country"` // 同理
}
func postWaitingGames(w http.ResponseWriter, r *http.Request) {
// ✅ 1. 确保 Body 关闭(defer 在函数开头)
defer r.Body.Close()
// ✅ 2. 安全读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
fmt.Printf("Read error: %v\n", err)
return
}
// ✅ 3. 反序列化
var game Game
if err := json.Unmarshal(body, &game); err != nil {
http.Error(w, "Invalid JSON format", http.StatusBadRequest)
fmt.Printf("Unmarshal error: %v\n", err)
return
}
// ✅ 4. 打印并返回成功响应
fmt.Printf("Received game: %+v\n", game)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "success",
"message": fmt.Sprintf("Game RID=%s registered", game.RID),
})
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/wait/", postWaitingGames).Methods("POST")
fmt.Println("Server starting on :8081...")
http.ListenAndServe(":8081", router)
}⚠️ 关键修正点说明:
-
JSON 标签无空格:
json:"RID"✅,非json: "RID"❌(后者会被 Go 忽略,导致字段无法映射); -
defer r.Body.Close()提前:避免ReadAll失败后未关闭 Body; -
错误处理完备:对
io.ReadAll和json.Unmarshal均做显式错误检查,并返回对应 HTTP 状态码; -
响应规范化:使用
json.NewEncoder(w)安全输出 JSON 响应,避免手动拼接。
? 验证步骤
- 启动 Go 服务:
go run main.go - 运行修正后的 Python 脚本
- 观察终端输出:
Received game: {RID:"8" Country:"Australia"} - 检查 HTTP 响应状态码为
200,响应体为标准 JSON
? 总结
- 客户端:使用
requests.post(json=dict)时,永远传原始字典,勿手动json.dumps(); - 服务端:确保结构体 JSON 标签格式正确(
json:"field"),始终校验ReadAll和Unmarshal错误,并合理管理资源生命周期; - 调试技巧:可在 Go 中打印原始
body(fmt.Printf("Raw body: %s\n", body))快速确认是否收到预期 JSON 对象。
遵循以上实践,即可稳定、安全地完成 JSON POST 请求的接收与解析。


















