diff --git a/mapstructure.go b/mapstructure.go index 6cb703cf..3643901f 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -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()) } @@ -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] == "-" { @@ -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 { diff --git a/mapstructure_bugs_test.go b/mapstructure_bugs_test.go index 579d179c..bcfc5054 100644 --- a/mapstructure_bugs_test.go +++ b/mapstructure_bugs_test.go @@ -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") + } +} diff --git a/mapstructure_test.go b/mapstructure_test.go index ccd474c1..53b2d0a4 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -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{}{}