support struct pointer (#1342)

* support struct pointer

* add readme
This commit is contained in:
田欧 2018-05-01 14:24:18 +08:00 committed by Bo-Yi Wu
parent 2282be059b
commit bd4f73af67
3 changed files with 151 additions and 1 deletions

View File

@ -51,6 +51,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Run multiple service using Gin](#run-multiple-service-using-gin) - [Run multiple service using Gin](#run-multiple-service-using-gin)
- [Graceful restart or stop](#graceful-restart-or-stop) - [Graceful restart or stop](#graceful-restart-or-stop)
- [Build a single binary with templates](#build-a-single-binary-with-templates) - [Build a single binary with templates](#build-a-single-binary-with-templates)
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
- [Testing](#testing) - [Testing](#testing)
- [Users](#users--) - [Users](#users--)
@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) {
See a complete example in the `examples/assets-in-binary` directory. See a complete example in the `examples/assets-in-binary` directory.
### Bind form-data request with custom struct
The follow example using custom struct:
```go
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"`
}
type StructD struct {
NestedAnonyStruct struct {
FieldX string `form:"field_x"`
}
FieldD string `form:"field_d"`
}
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 GetDataD(c *gin.Context) {
var b StructD
c.Bind(&b)
c.JSON(200, gin.H{
"x": b.NestedAnonyStruct,
"d": b.FieldD,
})
}
func main() {
r := gin.Default()
r.GET("/getb", GetDataB)
r.GET("/getc", GetDataC)
r.GET("/getd", GetDataD)
r.Run()
}
```
Using the command `curl` command result:
```
$ 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"}
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
{"d":"world","x":{"FieldX":"hello"}}
```
**NOTE**: NOT support the follow style struct:
```go
type StructX struct {
X struct {} `form:"name_x"` // HERE have form
}
type StructY struct {
Y StructX `form:"name_y"` // HERE hava form
}
type StructZ struct {
Z *StructZ `form:"name_z"` // HERE hava form
}
```
In a word, only support nested custom struct which have no `form` now.
## Testing ## Testing
The `net/http/httptest` package is preferable way for HTTP testing. The `net/http/httptest` package is preferable way for HTTP testing.

View File

@ -74,6 +74,18 @@ type FooStructForSliceType struct {
SliceFoo []int `form:"slice_foo"` SliceFoo []int `form:"slice_foo"`
} }
type FooStructForStructType struct {
StructFoo struct {
Idx int `form:"idx"`
}
}
type FooStructForStructPointerType struct {
StructPointerFoo *struct {
Name string `form:"name"`
}
}
type FooStructForSliceMapType struct { type FooStructForSliceMapType struct {
// Unknown type: not support map // Unknown type: not support map
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) {
testFormBindingForType(t, "GET", testFormBindingForType(t, "GET",
"/?ptr_bar=test", "/?bar2=test", "/?ptr_bar=test", "/?bar2=test",
"", "", "Ptr") "", "", "Ptr")
testFormBindingForType(t, "POST",
"/", "/",
"idx=123", "id1=1", "Struct")
testFormBindingForType(t, "GET",
"/?idx=123", "/?id1=1",
"", "", "Struct")
testFormBindingForType(t, "POST",
"/", "/",
"name=thinkerou", "name1=ou", "StructPointer")
testFormBindingForType(t, "GET",
"/?name=thinkerou", "/?name1=ou",
"", "", "StructPointer")
} }
func TestBindingQuery(t *testing.T) { func TestBindingQuery(t *testing.T) {
@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Struct":
obj := FooStructForStructType{}
err := b.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t,
struct {
Idx int "form:\"idx\""
}(struct {
Idx int "form:\"idx\""
}{Idx: 123}),
obj.StructFoo)
case "StructPointer":
obj := FooStructForStructPointerType{}
err := b.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t,
struct {
Name string "form:\"name\""
}(struct {
Name string "form:\"name\""
}{Name: "thinkerou"}),
*obj.StructPointerFoo)
case "Map": case "Map":
obj := FooStructForMapType{} obj := FooStructForMapType{}
err := b.Bind(req, &obj) err := b.Bind(req, &obj)

View File

@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
if inputFieldName == "" { if inputFieldName == "" {
inputFieldName = typeField.Name inputFieldName = typeField.Name
// if "form" tag is nil, we inspect if the field is a struct. // if "form" tag is nil, we inspect if the field is a struct or struct pointer.
// this would not make sense for JSON parsing but it does for a form // this would not make sense for JSON parsing but it does for a form
// since data is flatten // since data is flatten
if structFieldKind == reflect.Ptr {
if !structField.Elem().IsValid() {
structField.Set(reflect.New(structField.Type().Elem()))
}
structField = structField.Elem()
structFieldKind = structField.Kind()
}
if structFieldKind == reflect.Struct { if structFieldKind == reflect.Struct {
err := mapForm(structField.Addr().Interface(), form) err := mapForm(structField.Addr().Interface(), form)
if err != nil { if err != nil {