From 48c9404337e05382403108e725d91768aa92447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Mon, 31 Oct 2022 15:17:13 +0100 Subject: [PATCH 1/2] Fix struct to typed map Add struct to generic map test Add struct to typed map test --- mapstructure.go | 25 ++++++++++++----- mapstructure_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) 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..39cac6e8 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2788,6 +2788,70 @@ func testArrayInput(t *testing.T, input map[string]interface{}, expected *Array) } } +func TestDecode_structToGenericMap(t *testing.T) { + type SourceChild struct { + String string `mapstructure:"string"` + } + + type SourceParent struct { + Child SourceChild `mapstructure:"child"` + } + + var target map[string]interface{} + + source := SourceParent{ + Child: SourceChild{ + String: "hello", + }, + } + + if err := Decode(source, &target); err != nil { + t.Fatalf("got error: %s", err) + } + + expected := map[string]interface{}{ + "child": map[string]interface{}{ + "string": "hello", + }, + } + + 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"` + } + + type SourceParent struct { + Child SourceChild `mapstructure:"child"` + } + + var target map[string]map[string]interface{} + + source := SourceParent{ + Child: SourceChild{ + String: "hello", + }, + } + + if err := Decode(source, &target); err != nil { + t.Fatalf("got error: %s", err) + } + + expected := map[string]map[string]interface{}{ + "child": { + "string": "hello", + }, + } + + 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 } From aecdbacb986ca13b4183b8d24c8df17346f91b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Mon, 31 Oct 2022 15:33:59 +0100 Subject: [PATCH 2/2] Add more test data to struct to map tests --- mapstructure_test.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/mapstructure_test.go b/mapstructure_test.go index 39cac6e8..96fa4fdb 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2790,7 +2790,9 @@ func testArrayInput(t *testing.T, input map[string]interface{}, expected *Array) func TestDecode_structToGenericMap(t *testing.T) { type SourceChild struct { - String string `mapstructure:"string"` + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Map map[string]float32 `mapstructure:"map"` } type SourceParent struct { @@ -2802,6 +2804,11 @@ func TestDecode_structToGenericMap(t *testing.T) { source := SourceParent{ Child: SourceChild{ String: "hello", + Int: 1, + Map: map[string]float32{ + "one": 1.0, + "two": 2.0, + }, }, } @@ -2812,6 +2819,11 @@ func TestDecode_structToGenericMap(t *testing.T) { expected := map[string]interface{}{ "child": map[string]interface{}{ "string": "hello", + "int": 1, + "map": map[string]float32{ + "one": 1.0, + "two": 2.0, + }, }, } @@ -2822,7 +2834,9 @@ func TestDecode_structToGenericMap(t *testing.T) { func TestDecode_structToTypedMap(t *testing.T) { type SourceChild struct { - String string `mapstructure:"string"` + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Map map[string]float32 `mapstructure:"map"` } type SourceParent struct { @@ -2834,6 +2848,11 @@ func TestDecode_structToTypedMap(t *testing.T) { source := SourceParent{ Child: SourceChild{ String: "hello", + Int: 1, + Map: map[string]float32{ + "one": 1.0, + "two": 2.0, + }, }, } @@ -2844,6 +2863,11 @@ func TestDecode_structToTypedMap(t *testing.T) { expected := map[string]map[string]interface{}{ "child": { "string": "hello", + "int": 1, + "map": map[string]float32{ + "one": 1.0, + "two": 2.0, + }, }, }