JSON
是前後端交互的重要數據類型之一,使用Gin Web框架
可以很方便地將HTTP請求報文中JSON
格式的Body
數據解析到結構體Struct
或字典Map
數據結構中。
Golang Gin 優雅地解析JSON請求數據
環境
go version go1.14.3 windows/amd64
github.com/gin-gonic/gin v1.6.3
1. 結論
參考 Fix #216: Enable to call binding multiple times in some formats #1341
使用場景 | 函數 |
---|---|
單次綁定 | ShouldBindJSON > BindJSON |
多次綁定⭐ | ShouldBindBodyWith |
ShouldBindJSON
方法是最常用解析JSON數據的方法之一,但在重複調用的情況下會出現EOF
的報錯,這個原因出在ShouldBindJSON
在調用過一次之後context.request.body.sawEOF
的值是false
導致,所以如果要多次綁定多個變量,需要使用ShouldBindBodyWith
。
至於爲什麼單次綁定不優選使用BindJSON
方法,主要因爲BindJSON
方法會強制拋出錯誤,影響正常流程。
以下爲範例:
// 以下是用於存儲JSON數據的結構體
type MsgJson struct {
Msg string `json:"msg"`
}
// 單次綁定使用 ShouldBindJSON 方法
func bindExample(c *gin.Context) {
// ---> 聲明結構體變量
var a MsgJson
// ---> 綁定數據
if err := c.ShouldBindJSON(&a); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
// --> 返回
c.JSON(http.StatusOK, gin.H{"msg": "ok"})
return
}
// 多次綁定優先使用 ShouldBindBodyWith 方法
func bindWithRightWay(c *gin.Context) {
// ---> 聲明兩個結構體變量用於存儲JSON數據
var a, b MsgJson
// ---> 第一次解析(注意第二個參數是 binding.JSON)
if err := c.ShouldBindBodyWith(&a, binding.JSON); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
// ---> 第二次解析
if err := c.ShouldBindBodyWith(&b, binding.JSON); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
// ---> 返回
c.JSON(http.StatusOK, gin.H{"msg": "ok"})
return
}
2. EOF錯誤復現
EOF
錯誤出現在第二次使用ShouldBindJSON
方法,在多次綁定的情況下,優先使用ShouldBindBodyWith
,以下爲❌錯誤示範:
type MsgJson struct {
Msg string `json:"msg"`
}
func main() {
r := gin.Default()
r.POST("/v2", bindWithError)
_ = r.Run("127.0.0.1:9001")
}
func bindWithError(c *gin.Context) {
var a, b MsgJson
if err := c.ShouldBindJSON(&a); err != nil {....}
// ---> 注意,這裏會出現EOF報錯
if err := c.ShouldBindJSON(&b); err != nil {....}
.......
return
}
Postman
測試結果:
Goland
斷點調試:
3. ShouldBindBodyWith
源碼分析
ShouldBindBodyWith
和ShouldBindWith
很像,但它保存了requests
的Body
到上下文,允許Body
被繼續調用。
注意:這個方法會先讀取Body
然後綁定,如果只綁定一次,建議使用ShouldBindWith
來獲得更好的性能(因爲後者會直接讀取並寫到指定變量,而沒有寫入上下文)。
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
var body []byte
// ---> 先看上下文是否已經有Body的Bytes數據
if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok {
body = cbb
}
}
// ---> 如果Body不爲空的情況下,讀取Body數據並寫入上下文
if body == nil {
body, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
return err
}
c.Set(BodyBytesKey, body)
}
return bb.BindBody(body, obj)
}
流程圖