Skip to content

Commit 541cf33

Browse files
Merge pull request #144 from jphastings/fix-slice-deep-map
Fix slice deep map (owned)
2 parents cac4a14 + 2cf92ca commit 541cf33

2 files changed

Lines changed: 114 additions & 13 deletions

File tree

mapstructure.go

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,13 @@ type DecoderConfig struct {
281281
// }
282282
Squash bool
283283

284+
// Deep will map structures in slices instead of copying them
285+
//
286+
// type Parent struct {
287+
// Children []Child `mapstructure:",deep"`
288+
// }
289+
Deep bool
290+
284291
// Metadata is the struct that will contain extra metadata about
285292
// the decoding. If this is nil, then no metadata will be tracked.
286293
Metadata *Metadata
@@ -1070,6 +1077,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
10701077
// If Squash is set in the config, we squash the field down.
10711078
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
10721079

1080+
// If Deep is set in the config, set as default value.
1081+
deep := d.config.Deep
1082+
10731083
v = dereferencePtrToStructIfNeeded(v, d.config.TagName)
10741084

10751085
// Determine the name of the key in the map
@@ -1118,6 +1128,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
11181128
continue
11191129
}
11201130
}
1131+
1132+
deep = deep || strings.Index(tagValue[index+1:], "deep") != -1
1133+
11211134
if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" {
11221135
keyName = keyNameTagValue
11231136
}
@@ -1164,6 +1177,41 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
11641177
valMap.SetMapIndex(reflect.ValueOf(keyName), vMap)
11651178
}
11661179

1180+
case reflect.Slice:
1181+
if deep {
1182+
var childType reflect.Type
1183+
switch v.Type().Elem().Kind() {
1184+
case reflect.Struct:
1185+
childType = reflect.TypeOf(map[string]interface{}{})
1186+
default:
1187+
childType = v.Type().Elem()
1188+
}
1189+
1190+
sType := reflect.SliceOf(childType)
1191+
1192+
addrVal := reflect.New(sType)
1193+
1194+
vSlice := reflect.MakeSlice(sType, v.Len(), v.Cap())
1195+
1196+
if v.Len() > 0 {
1197+
reflect.Indirect(addrVal).Set(vSlice)
1198+
1199+
err := d.decode(keyName, v.Interface(), reflect.Indirect(addrVal))
1200+
if err != nil {
1201+
return err
1202+
}
1203+
}
1204+
1205+
vSlice = reflect.Indirect(addrVal)
1206+
1207+
valMap.SetMapIndex(reflect.ValueOf(keyName), vSlice)
1208+
1209+
break
1210+
}
1211+
1212+
// When deep mapping is not needed, fallthrough to normal copy
1213+
fallthrough
1214+
11671215
default:
11681216
valMap.SetMapIndex(reflect.ValueOf(keyName), v)
11691217
}
@@ -1718,13 +1766,24 @@ func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool,
17181766
}
17191767

17201768
func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
1721-
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
1769+
1770+
if v.Kind() != reflect.Ptr {
17221771
return v
17231772
}
1724-
deref := v.Elem()
1725-
derefT := deref.Type()
1726-
if isStructTypeConvertibleToMap(derefT, true, tagName) {
1727-
return deref
1773+
1774+
switch v.Elem().Kind() {
1775+
case reflect.Slice:
1776+
return v.Elem()
1777+
1778+
case reflect.Struct:
1779+
deref := v.Elem()
1780+
derefT := deref.Type()
1781+
if isStructTypeConvertibleToMap(derefT, true, tagName) {
1782+
return deref
1783+
}
1784+
return v
1785+
1786+
default:
1787+
return v
17281788
}
1729-
return v
17301789
}

mapstructure_test.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3577,13 +3577,6 @@ func testArrayInput(t *testing.T, input map[string]any, expected *Array) {
35773577
}
35783578
}
35793579

3580-
func stringPtr(v string) *string { return &v }
3581-
func intPtr(v int) *int { return &v }
3582-
func uintPtr(v uint) *uint { return &v }
3583-
func boolPtr(v bool) *bool { return &v }
3584-
func floatPtr(v float64) *float64 { return &v }
3585-
func interfacePtr(v any) *any { return &v }
3586-
35873580
// Test struct for embedded error message testing
35883581
type TestDatabaseConfig struct {
35893582
Host string `mapstructure:"host"`
@@ -3638,3 +3631,52 @@ func TestDecoder_ErrorUnused_EmbeddedStruct_QualifiedTypeName(t *testing.T) {
36383631
t.Errorf("Expected error message to contain 'invalid_key', got: %s", errorMessage)
36393632
}
36403633
}
3634+
func TestDecode_structArrayDeepMap(t *testing.T) {
3635+
type SourceChild struct {
3636+
String string `mapstructure:"some-string"`
3637+
}
3638+
3639+
type SourceParent struct {
3640+
ChildrenA []SourceChild `mapstructure:"children-a,deep"`
3641+
ChildrenB *[]SourceChild `mapstructure:"children-b,deep"`
3642+
}
3643+
3644+
var target map[string]interface{}
3645+
3646+
source := SourceParent{
3647+
ChildrenA: []SourceChild{
3648+
{String: "one"},
3649+
{String: "two"},
3650+
},
3651+
ChildrenB: &[]SourceChild{
3652+
{String: "one"},
3653+
{String: "two"},
3654+
},
3655+
}
3656+
3657+
if err := Decode(source, &target); err != nil {
3658+
t.Fatalf("got error: %s", err)
3659+
}
3660+
3661+
expected := map[string]interface{}{
3662+
"children-a": []map[string]interface{}{
3663+
{"some-string": "one"},
3664+
{"some-string": "two"},
3665+
},
3666+
"children-b": []map[string]interface{}{
3667+
{"some-string": "one"},
3668+
{"some-string": "two"},
3669+
},
3670+
}
3671+
3672+
if !reflect.DeepEqual(target, expected) {
3673+
t.Fatalf("failed: \nexpected: %#v\nresult: %#v", expected, target)
3674+
}
3675+
}
3676+
3677+
func stringPtr(v string) *string { return &v }
3678+
func intPtr(v int) *int { return &v }
3679+
func uintPtr(v uint) *uint { return &v }
3680+
func boolPtr(v bool) *bool { return &v }
3681+
func floatPtr(v float64) *float64 { return &v }
3682+
func interfacePtr(v interface{}) *interface{} { return &v }

0 commit comments

Comments
 (0)