Skip to content

Commit

Permalink
decodeMapFromStruct: Only deref element if we're squashing the struct
Browse files Browse the repository at this point in the history
Fixes #231

This also reverts the test _change_ that was introduced in 231 back to
the original test. This retains the _new tests_ added in 231 to verify
the behavior of 231.
  • Loading branch information
mitchellh committed Jan 12, 2021
1 parent 1b4332d commit cbfe794
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 26 deletions.
17 changes: 11 additions & 6 deletions mapstructure.go
Expand Up @@ -894,10 +894,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.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
// Handle embedded struct pointers as embedded structs.
v = v.Elem()
}
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())
}
Expand All @@ -907,6 +903,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re

// If Squash is set in the config, we squash the field down.
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous

// Determine the name of the key in the map
if index := strings.Index(tagValue, ","); index != -1 {
if tagValue[:index] == "-" {
Expand All @@ -919,8 +916,16 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re

// If "squash" is specified in the tag, we squash the field down.
squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
if squash && v.Kind() != reflect.Struct {
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
if squash {
// When squashing, the embedded type can be a pointer to a struct.
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
v = v.Elem()
}

// The final type must be a struct
if v.Kind() != reflect.Struct {
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
}
}
keyName = tagValue[:index]
} else if len(tagValue) > 0 {
Expand Down
34 changes: 34 additions & 0 deletions mapstructure_bugs_test.go
Expand Up @@ -567,3 +567,37 @@ func TestDecode_weakEmptyStringToInt(t *testing.T) {
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
}
}

// GH-228: Squash cause *time.Time set to zero
func TestMapSquash(t *testing.T) {
type AA struct {
T *time.Time
}
type A struct {
AA
}

v := time.Now()
in := &AA{
T: &v,
}
out := &A{}
d, err := NewDecoder(&DecoderConfig{
Squash: true,
Result: out,
})
if err != nil {
t.Fatalf("err: %s", err)
}
if err := d.Decode(in); err != nil {
t.Fatalf("err: %s", err)
}

// these failed
if !v.Equal(*out.T) {
t.Fatal("expected equal")
}
if out.T.IsZero() {
t.Fatal("expected false")
}
}
21 changes: 1 addition & 20 deletions mapstructure_test.go
Expand Up @@ -2390,26 +2390,7 @@ func TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues(t *testing.T) {
"visible-map": emptyMap,
"omittable-map": map[string]interface{}{"k": "v"},
"visible-nested": emptyNested,
"omittable-nested": map[string]interface{}{
"Vbar": map[string]interface{}{
"Vbool": false,
"Vdata": interface{}(nil),
"Vextra": "",
"Vfloat": float64(0),
"Vint": 0,
"Vint16": int16(0),
"Vint32": int32(0),
"Vint64": int64(0),
"Vint8": int8(0),
"VjsonFloat": float64(0),
"VjsonInt": 0,
"VjsonNumber": json.Number(""),
"VjsonUint": uint(0),
"Vstring": "",
"Vuint": uint(0),
},
"Vfoo": "",
},
"omittable-nested": &Nested{},
}

actual := &map[string]interface{}{}
Expand Down

0 comments on commit cbfe794

Please sign in to comment.