gin作為go語言最知名的網路庫,在這裡我簡要介紹一下url的查詢參數解析。主要是這裡面存在一些需要註意的地方。這裡,直接給出代碼,和運行結果,在必要的地方進行分析。 代碼1: 測試結果: 輸出結果: curl "http://localhost:8080/getb?field_a=hello&fi ...
gin作為go語言最知名的網路庫,在這裡我簡要介紹一下url的查詢參數解析。主要是這裡面存在一些需要註意的地方。這裡,直接給出代碼,和運行結果,在必要的地方進行分析。
代碼1:
type StructA struct { FieldA string `form:"field_a"` } type StructB struct { NestedStruct StructA FieldB string `form:"field_b"` } type StructC struct { NestedStructPointer *StructA FieldC string `form:"field_c"` } func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) } func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStructPointer, "c": b.FieldC, }) } func main() { r := gin.Default() r.GET("/getb", GetDataB) r.GET("/getc", GetDataC) r.Run() }
測試結果:
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" {"a":{"FieldA":"hello"},"b":"world"} $ curl "http://localhost:8080/getc?field_a=hello&field_c=world" {"a":{"FieldA":"hello"},"c":"world"}
上述結果顯示gin的query解析可以嵌套賦值,只需要form tag和傳入的參數一致。
再看下麵的代碼:
代碼2:
package main import ( "github.com/gin-gonic/gin" ) type StructA struct { FieldA string `form:"field_a"` } type StructB struct { StructA FieldB string `form:"field_b"` } type StructC struct { *StructA FieldC string `form:"field_c"` } func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H{ "a": b.FieldA, "b": b.FieldB, }) } func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(200, gin.H{ "a": b.FieldA, "c": b.FieldC, }) } func main() { r := gin.Default() r.GET("/getb", GetDataB) r.GET("/getc", GetDataC) r.Run() }
輸出結果:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":"hello","b":"world"}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":"hello","c":"world"}
結果顯示,gin的url查詢參數解析可以正常處理嵌套的結構體,只需要form tag和傳入的參數一致。
再看下麵代碼:
代碼3:
package main import ( "github.com/gin-gonic/gin" ) type structA struct { FieldA string `form:"field_a"` } type StructB struct { structA FieldB string `form:"field_b"` } type StructC struct { *structA FieldC string `form:"field_c"` } func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H{ "a": b.FieldA, "b": b.FieldB, }) } func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(200, gin.H{ "a": b.FieldA, "c": b.FieldC, }) } func main() { r := gin.Default() r.GET("/getb", GetDataB) r.GET("/getc", GetDataC) r.Run() }
註意,上述代碼只是將StructA改為structA,也就是說大小寫變化。測試結果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":"","b":"world"}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
客戶端顯示為空,伺服器列印一個panic語句,錯誤類型是runtime error: invalid memory address or nil pointer dereference,一系列panic堆棧,然後是:
[GIN] 2019/04/11 - 22:07:01 | 500 | 2.403482ms | 127.0.0.1 | GET /getc?field_a=hello&field_c=world,顯示狀態碼是500。
可見,對於結構體嵌套解析,只有結構體是大寫的情況下才可以有效解析,小寫的情況下,要麼不解析,要麼解析出現異常。
下麵再看一段代碼,官方提示的錯誤:
代碼4:
package main import ( "github.com/gin-gonic/gin" ) type StructA struct { FieldA string } type StructB struct { NestedStruct StructA `form:"field_a"` FieldB string `form:"field_b"` } type StructC struct { NestedStructPointer *StructA `form:"field_a"` FieldC string `form:"field_c"` } func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) } func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStructPointer, "c": b.FieldC, }) } func main() { r := gin.Default() r.GET("/getb", GetDataB) r.GET("/getc", GetDataC) r.Run() }
這裡註意StructA結構體的tag的位置發生了變化。結果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":""},"b":""}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":null,"c":""}
可見,這種書寫tag的方式存在問題,對應的結構體成員不能正確的解析。)
關於其他情況, 經過測試
(1)對於代碼1,將其中的StructA改寫外structA,參數可以正確解析。
(2)對於代碼1,將NestedStruct改為nestedStruct, NestedStructPointer改為nestedStructPointer,對應欄位不再解析。
關於tag中的binding:"required"參數,我有一點需要補充的,下麵為實例代碼:
代碼5:
package main import ( "github.com/gin-gonic/gin" ) type StructA struct { FieldA int `form:"field_a" binding:"required"` } type StructB struct { NestedStruct StructA FieldB string `form:"field_b" binding:"required"` } func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(200, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) } func main() { r := gin.Default() r.GET("/getb", GetDataB) r.Run() }
註意FieldA的類型和tag,類型由String改為int, tag添加了`bind:"required"`。測試結果如下:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":0},"b":""}
服務端有一個額外的列印:
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_a=0&field_b=world"
{"a":{"FieldA":0},"b":"world"}
服務端有一個額外的列印:
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_b=world"
{"a":{"FieldA":0},"b":"world"}
服務端有一個額外的列印:
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200
curl "http://localhost:8080/getb?field_a=1&field_b=world"
{"a":{"FieldA":1},"b":"world"}
服務端沒有額外的列印。
curl "http://localhost:8080/getb?field_a=1&field_b="
{"a":{"FieldA":1},"b":""}
服務端有一個額外的列印:
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200
可見,gin對於bind:"required"的判斷非常簡單,對於int類型,是判斷這個int類型是否為0,無論是否顯示設置的0,都視為參數錯誤,對於字元串參數來說,是判斷是否為空字元串。