From d905a35c2108c13ac240ffb4e3b7c5f0c2343b46 Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Wed, 23 Jul 2025 12:02:14 +0200 Subject: [PATCH] feat(form): support BindUnmarshaler slice elements gin-gonic#4312 --- binding/form_mapping.go | 16 +++++++++++- binding/form_mapping_test.go | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 45a39e15..d6944955 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -183,8 +183,15 @@ type BindUnmarshaler interface { // If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true` // to skip the default value setting. func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { - switch v := value.Addr().Interface().(type) { + if value.Kind() != reflect.Ptr { + value = value.Addr() + } + switch value.Interface().(type) { case BindUnmarshaler: + if !value.Elem().IsValid() { + value.Set(reflect.New(value.Type().Elem())) + } + v := value.Interface().(BindUnmarshaler) return true, v.UnmarshalParam(val) } return false, nil @@ -449,6 +456,13 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { + if ok, err := trySetCustom(s, value.Index(i)); ok { + if err != nil { + return err + } else { + continue + } + } err := setWithProperType(s, value.Index(i), field) if err != nil { return err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 55b967a3..0acb53d2 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -550,6 +550,54 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { assert.Equal(t, "happiness", s.FileData.Name) } +func TestMappingCustomStructTypeSliceWithFormTag(t *testing.T) { + var s struct { + FileData []customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`, `http:/bar:sadness`}}, "form") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData[0].Protocol) + assert.Equal(t, "/foo", s.FileData[0].Path) + assert.Equal(t, "happiness", s.FileData[0].Name) + + assert.Equal(t, "http", s.FileData[1].Protocol) + assert.Equal(t, "/bar", s.FileData[1].Path) + assert.Equal(t, "sadness", s.FileData[1].Name) +} + +func TestMappingCustomStructTypeSliceWithURITag(t *testing.T) { + var s struct { + FileData []customUnmarshalParamType `uri:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`, `http:/bar:sadness`}}, "uri") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData[0].Protocol) + assert.Equal(t, "/foo", s.FileData[0].Path) + assert.Equal(t, "happiness", s.FileData[0].Name) + + assert.Equal(t, "http", s.FileData[1].Protocol) + assert.Equal(t, "/bar", s.FileData[1].Path) + assert.Equal(t, "sadness", s.FileData[1].Name) +} + +func TestMappingCustomPointerStructTypeSliceWithFormTag(t *testing.T) { + var s struct { + FileData []*customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`, `http:/bar:sadness`}}, "form") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData[0].Protocol) + assert.Equal(t, "/foo", s.FileData[0].Path) + assert.Equal(t, "happiness", s.FileData[0].Name) + + assert.Equal(t, "http", s.FileData[1].Protocol) + assert.Equal(t, "/bar", s.FileData[1].Path) + assert.Equal(t, "sadness", s.FileData[1].Name) +} + type customPath []string func (p *customPath) UnmarshalParam(param string) error {