
在使用 go-json-rest 框架时,若在调用 r.DecodeJsonPayload() 前意外读取并耗尽 r.Body(如通过 ioutil.ReadAll),会导致后续 JSON 解析失败并报错“JSON payload is empty”。根本原因在于 HTTP 请求体是单次读取流,不可重复读取。
在使用 go-json-rest 框架时,若在调用 `r.decodejsonpayload()` 前意外读取并耗尽 `r.body`(如通过 `ioutil.readall`),会导致后续 json 解析失败并报错“json payload is empty”。根本原因在于 http 请求体是单次读取流,不可重复读取。
go-json-rest 的 rest.Request.DecodeJsonPayload() 方法内部已完整处理请求体的读取、解析与错误反馈。开发者无需、也不应提前手动读取 r.Body —— 这一操作会将底层 io.ReadCloser 中的数据全部消费完毕,使后续解码器面对一个空的、已关闭或 EOF 状态的 body,从而触发 "JSON payload is empty" 错误。
以下为修复后的标准写法(关键:移除所有对 r.Body 的手动读取):
func CreateTodo(w rest.ResponseWriter, r *rest.Request) {
var todo Todo
// ✅ 正确:直接交由框架解析,无需预读 body
if err := r.DecodeJsonPayload(&todo); err != nil {
log.Printf("failed to decode JSON payload: %v", err)
rest.Error(w, "invalid JSON payload", http.StatusBadRequest)
return
}
// 注意:结构体字段必须导出(首字母大写)且带 JSON 标签,否则无法反序列化
if todo.Name == "" {
rest.Error(w, "todo name is required", http.StatusBadRequest)
return
}
lock.Lock()
defer lock.Unlock() // 推荐 defer 释放锁,避免遗漏
todo.ID = len(todos) // 使用导出字段 ID 和 Name(见下方结构体修正)
todos = append(todos, todo)
log.Printf("created todo with ID %d, name: %q", todo.ID, todo.Name)
w.WriteJson(&todo)
}同时,请务必修正你的 Todo 结构体:Go 的 JSON 反序列化要求字段必须可导出(即首字母大写),并建议显式添加 json 标签,否则字段将被忽略:
type Todo struct {
ID int `json:"id"`
Name string `json:"name"`
}⚠️ 注意事项:
- ❌ 禁止在
DecodeJsonPayload前调用ioutil.ReadAll(r.Body)、io.Copy、json.NewDecoder(r.Body).Decode()等任何消耗 body 的操作;- ✅ 如需调试原始 JSON 内容,应在解码后通过
json.Marshal打印(或使用中间件统一记录);- ✅
defer r.Body.Close()在go-json-rest中通常非必需(框架已自动管理),且不应放在解码前(可能提前关闭);- ✅ 始终检查
DecodeJsonPayload返回的err,它会准确反映 JSON 语法错误、类型不匹配或字段不可写等问题。
总结:go-json-rest 的 JSON 解析是原子且独占的。尊重请求体的单向流特性,让框架全权处理输入,是避免 "JSON payload is empty" 类错误的最简、最可靠实践。


















