Skip to content

Commit 60fc2fb

Browse files
authored
binder: make binding to Map work better with string destinations (#2554)
1 parent 226e4f0 commit 60fc2fb

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

bind.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,26 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
131131
typ := reflect.TypeOf(destination).Elem()
132132
val := reflect.ValueOf(destination).Elem()
133133

134-
// Map
135-
if typ.Kind() == reflect.Map {
134+
// Support binding to limited Map destinations:
135+
// - map[string][]string,
136+
// - map[string]string <-- (binds first value from data slice)
137+
// - map[string]interface{}
138+
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
139+
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
140+
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
141+
k := typ.Elem().Kind()
142+
isElemInterface := k == reflect.Interface
143+
isElemString := k == reflect.String
144+
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
145+
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
146+
return nil
147+
}
136148
for k, v := range data {
137-
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
149+
if isElemString {
150+
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
151+
} else {
152+
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
153+
}
138154
}
139155
return nil
140156
}

bind_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,62 @@ func TestBindUnsupportedMediaType(t *testing.T) {
429429
testBindError(t, strings.NewReader(invalidContent), MIMEApplicationJSON, &json.SyntaxError{})
430430
}
431431

432+
func TestDefaultBinder_bindDataToMap(t *testing.T) {
433+
exampleData := map[string][]string{
434+
"multiple": {"1", "2"},
435+
"single": {"3"},
436+
}
437+
438+
t.Run("ok, bind to map[string]string", func(t *testing.T) {
439+
dest := map[string]string{}
440+
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
441+
assert.Equal(t,
442+
map[string]string{
443+
"multiple": "1",
444+
"single": "3",
445+
},
446+
dest,
447+
)
448+
})
449+
450+
t.Run("ok, bind to map[string][]string", func(t *testing.T) {
451+
dest := map[string][]string{}
452+
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
453+
assert.Equal(t,
454+
map[string][]string{
455+
"multiple": {"1", "2"},
456+
"single": {"3"},
457+
},
458+
dest,
459+
)
460+
})
461+
462+
t.Run("ok, bind to map[string]interface", func(t *testing.T) {
463+
dest := map[string]interface{}{}
464+
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
465+
assert.Equal(t,
466+
map[string]interface{}{
467+
"multiple": []string{"1", "2"},
468+
"single": []string{"3"},
469+
},
470+
dest,
471+
)
472+
})
473+
474+
t.Run("ok, bind to map[string]int skips", func(t *testing.T) {
475+
dest := map[string]int{}
476+
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
477+
assert.Equal(t, map[string]int{}, dest)
478+
})
479+
480+
t.Run("ok, bind to map[string][]int skips", func(t *testing.T) {
481+
dest := map[string][]int{}
482+
assert.NoError(t, new(DefaultBinder).bindData(&dest, exampleData, "param"))
483+
assert.Equal(t, map[string][]int{}, dest)
484+
})
485+
486+
}
487+
432488
func TestBindbindData(t *testing.T) {
433489
ts := new(bindTestStruct)
434490
b := new(DefaultBinder)

0 commit comments

Comments
 (0)