diff --git a/mapstructure.go b/mapstructure.go index 7581806a..0c189c51 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -918,9 +918,6 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re // Next get the actual value of this field and verify it is assignable // to the map value. v := dataVal.Field(i) - if !v.Type().AssignableTo(valMap.Type().Elem()) { - return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) - } tagValue := f.Tag.Get(d.config.TagName) keyName := f.Name @@ -973,9 +970,21 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re x := reflect.New(v.Type()) x.Elem().Set(v) - vType := valMap.Type() - vKeyType := vType.Key() - vElemType := vType.Elem() + var vKeyType reflect.Type + var vElemType reflect.Type + switch valMap.Type().Elem().Kind() { + case reflect.Map: + // When the target field is a typed map, use the map type + vType := valMap.Type().Elem() + vKeyType = vType.Key() + vElemType = vType.Elem() + + default: + // For any other target field type, use the root map type (map[string]interface{}) + vKeyType = valMap.Type().Key() + vElemType = valMap.Type().Elem() + } + mType := reflect.MapOf(vKeyType, vElemType) vMap := reflect.MakeMap(mType) @@ -1004,6 +1013,10 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re } default: + if !v.Type().AssignableTo(valMap.Type().Elem()) { + return fmt.Errorf("cannot assign type '%s' to map value field of type '%s'", v.Type(), valMap.Type().Elem()) + } + valMap.SetMapIndex(reflect.ValueOf(keyName), v) } } diff --git a/mapstructure_test.go b/mapstructure_test.go index d31129d7..96fa4fdb 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2788,6 +2788,94 @@ func testArrayInput(t *testing.T, input map[string]interface{}, expected *Array) } } +func TestDecode_structToGenericMap(t *testing.T) { + type SourceChild struct { + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Map map[string]float32 `mapstructure:"map"` + } + + type SourceParent struct { + Child SourceChild `mapstructure:"child"` + } + + var target map[string]interface{} + + source := SourceParent{ + Child: SourceChild{ + String: "hello", + Int: 1, + Map: map[string]float32{ + "one": 1.0, + "two": 2.0, + }, + }, + } + + if err := Decode(source, &target); err != nil { + t.Fatalf("got error: %s", err) + } + + expected := map[string]interface{}{ + "child": map[string]interface{}{ + "string": "hello", + "int": 1, + "map": map[string]float32{ + "one": 1.0, + "two": 2.0, + }, + }, + } + + if !reflect.DeepEqual(target, expected) { + t.Fatalf("bad: \nexpected: %#v\nresult: %#v", expected, target) + } +} + +func TestDecode_structToTypedMap(t *testing.T) { + type SourceChild struct { + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Map map[string]float32 `mapstructure:"map"` + } + + type SourceParent struct { + Child SourceChild `mapstructure:"child"` + } + + var target map[string]map[string]interface{} + + source := SourceParent{ + Child: SourceChild{ + String: "hello", + Int: 1, + Map: map[string]float32{ + "one": 1.0, + "two": 2.0, + }, + }, + } + + if err := Decode(source, &target); err != nil { + t.Fatalf("got error: %s", err) + } + + expected := map[string]map[string]interface{}{ + "child": { + "string": "hello", + "int": 1, + "map": map[string]float32{ + "one": 1.0, + "two": 2.0, + }, + }, + } + + if !reflect.DeepEqual(target, expected) { + t.Fatalf("bad: \nexpected: %#v\nresult: %#v", expected, target) + } +} + func stringPtr(v string) *string { return &v } func intPtr(v int) *int { return &v } func uintPtr(v uint) *uint { return &v }