Browse Source

support struct pointer (#1342)

* support struct pointer

* add readme
田欧 7 years ago
parent
commit
bd4f73af67
3 changed files with 151 additions and 1 deletions
  1. 93 0
      README.md
  2. 50 0
      binding/binding_test.go
  3. 8 1
      binding/form_mapping.go

+ 93 - 0
README.md

@@ -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.

+ 50 - 0
binding/binding_test.go

@@ -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)

+ 8 - 1
binding/form_mapping.go

@@ -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 {