Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

binding: support map for map form #1387

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions binding/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ type FooStructForTimeTypeFailLocation struct {
}

type FooStructForMapType struct {
// Unknown type: not support map
MapFoo map[string]interface{} `form:"map_foo"`
}

Expand Down Expand Up @@ -292,7 +291,7 @@ func TestBindingFormInvalidName2(t *testing.T) {
func TestBindingFormForType(t *testing.T) {
testFormBindingForType(t, "POST",
"/", "/",
"map_foo=", "bar2=1", "Map")
"map_foo={\"bar\":123}", "map_foo=1", "Map")

testFormBindingForType(t, "POST",
"/", "/",
Expand Down Expand Up @@ -509,8 +508,14 @@ func createDefaultFormPostRequest() *http.Request {
return req
}

func createFormPostRequestFail() *http.Request {
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
func createFormPostRequestForMap() *http.Request {
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
req.Header.Set("Content-Type", MIMEPOSTForm)
return req
}

func createFormPostRequestForMapFail() *http.Request {
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
req.Header.Set("Content-Type", MIMEPOSTForm)
return req
}
Expand All @@ -529,14 +534,27 @@ func createFormMultipartRequest() *http.Request {
return req
}

func createFormMultipartRequestFail() *http.Request {
func createFormMultipartRequestForMap() *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
mw := multipart.NewWriter(body)
defer mw.Close()

mw.SetBoundary(boundary)
mw.WriteField("map_foo", "bar")
mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req
}

func createFormMultipartRequestForMapFail() *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
mw := multipart.NewWriter(body)
defer mw.Close()

mw.SetBoundary(boundary)
mw.WriteField("map_foo", "3.14")
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req
Expand All @@ -561,8 +579,16 @@ func TestBindingDefaultValueFormPost(t *testing.T) {
assert.Equal(t, "hello", obj.Bar)
}

func TestBindingFormPostFail(t *testing.T) {
req := createFormPostRequestFail()
func TestBindingFormPostForMap(t *testing.T) {
req := createFormPostRequestForMap()
var obj FooStructForMapType
err := FormPost.Bind(req, &obj)
assert.Nil(t, err)
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
}

func TestBindingFormPostForMapFail(t *testing.T) {
req := createFormPostRequestForMapFail()
var obj FooStructForMapType
err := FormPost.Bind(req, &obj)
assert.Error(t, err)
Expand All @@ -578,8 +604,18 @@ func TestBindingFormMultipart(t *testing.T) {
assert.Equal(t, "foo", obj.Bar)
}

func TestBindingFormMultipartFail(t *testing.T) {
req := createFormMultipartRequestFail()
func TestBindingFormMultipartForMap(t *testing.T) {
req := createFormMultipartRequestForMap()
var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj)
assert.Nil(t, err)
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string))
assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64))
}

func TestBindingFormMultipartForMapFail(t *testing.T) {
req := createFormMultipartRequestForMapFail()
var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj)
assert.Error(t, err)
Expand Down Expand Up @@ -1061,7 +1097,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
case "Map":
obj := FooStructForMapType{}
err := b.Bind(req, &obj)
assert.Error(t, err)
assert.Nil(t, err)
thinkerou marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
case "SliceMap":
obj := FooStructForSliceMapType{}
err := b.Bind(req, &obj)
Expand Down
27 changes: 23 additions & 4 deletions binding/form_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package binding

import (
"encoding/json"
"errors"
"reflect"
"strconv"
Expand Down Expand Up @@ -82,10 +83,28 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
}
}
val.Field(i).Set(slice)
continue
}
if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
} else if structFieldKind == reflect.Map {
m := make(map[string]interface{})
err := json.Unmarshal([]byte(inputValue[0]), &m)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think better to use this ptr to real map. It's give you the opportunity to unmarshal from any types of map.

Suggested change
err := json.Unmarshal([]byte(inputValue[0]), &m)
err := json.Unmarshal([]byte(inputValue[0]), val.Field(i).Addr().Interface())

if err != nil {
return err
}

structField = reflect.Indirect(structField)
structField = reflect.MakeMap(structField.Type())
for k, v := range m {
structField.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}

val.Field(i).Set(structField)
} else {
if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
return err
}
continue
}
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
}
continue
Expand Down