diff --git a/binding/form_mapping.go b/binding/form_mapping.go index fa7ad1bc..2b0059d1 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -78,11 +78,23 @@ func mappingByPtr(ptr interface{}, setter setter, tag string) error { return err } -func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { +type structInfo struct { + value reflect.Value + field reflect.StructField +} + +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string, maped ...map[string]struct{}) (bool, error) { if field.Tag.Get(tag) == "-" { // just ignoring this field return false, nil } + var _maped map[string]struct{} + if len(maped) > 0 { + _maped = maped[0] + } else { + _maped = make(map[string]struct{}) + } + vKind := value.Kind() if vKind == reflect.Ptr { @@ -92,7 +104,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSet, err := mapping(vPtr.Elem(), field, setter, tag) + isSet, err := mapping(vPtr.Elem(), field, setter, tag, _maped) if err != nil { return false, err } @@ -116,22 +128,59 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag tValue := value.Type() var isSet bool + structs := make([]structInfo, 0) for i := 0; i < value.NumField(); i++ { sf := tValue.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } - ok, err := mapping(value.Field(i), sf, setter, tag) + tagValue := sf.Tag.Get(tag) + if _, ok := _maped[tagValue]; ok { + continue + } + + if isStruct(value.Field(i)) { + structs = append(structs, structInfo{ + value: value.Field(i), + field: sf, + }) + continue + } + ok, err := mapping(value.Field(i), sf, setter, tag, _maped) if err != nil { return false, err } isSet = isSet || ok + + if isSet && tagValue != "" { + _maped[tagValue] = struct{}{} + } + } + for _, st := range structs { + ok, err := mapping(st.value, st.field, setter, tag, _maped) + if err != nil { + return false, err + } + isSet = isSet || ok + tagValue := st.field.Tag.Get(tag) + if isSet && tagValue != "" { + _maped[tagValue] = struct{}{} + } } return isSet, nil } return false, nil } +func isStruct(_field reflect.Value) bool { + if _field.Kind() == reflect.Ptr { + vPtr := reflect.New(_field.Type().Elem()) + return vPtr.Elem().Kind() == reflect.Struct + } else { + return _field.Kind() == reflect.Struct + } +} + type setOptions struct { isDefaultExists bool defaultValue string diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 516554eb..36371abe 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -288,3 +288,56 @@ func TestMappingIgnoredCircularRef(t *testing.T) { err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) } + +func TestMappingNestedStructure(t *testing.T) { + formData := formSource{"name": {"hello"}, "id": {"1"}, "age": {"18"}} + type Other struct { + Name string `form:"name"` + } + type City struct { + Id int `form:"id"` + Name string `form:"name"` + OtherInfo *Other + } + type User struct { + Id int `form:"id"` + Name string `form:"name"` + Age int `form:"age"` + CityInfo *City + } + var u User + err := mappingByPtr(&u, formData, "form") + assert.NoError(t, err) + assert.Equal(t, u.Id, 1) + assert.Equal(t, u.Name, "hello") + assert.Nil(t, u.CityInfo) + + type User1 struct { + CityInfo *City + Id int `form:"id"` + Name string `form:"name"` + Age int `form:"age"` + } + var u1 User1 + + err = mappingByPtr(&u1, formData, "form") + assert.NoError(t, err) + assert.Equal(t, u1.Id, 1) + assert.Equal(t, u1.Name, "hello") + assert.Nil(t, u1.CityInfo) + + type User2 struct { + CityInfo City + Id int `form:"id"` + Name string `form:"name"` + Age int `form:"age"` + } + var u2 User2 + + err = mappingByPtr(&u2, formData, "form") + assert.NoError(t, err) + assert.Equal(t, u2.Id, 1) + assert.Equal(t, u2.Name, "hello") + assert.Equal(t, u2.CityInfo.Id, 0) + assert.Equal(t, u2.CityInfo.Name, "") +}