From 1d69ed7aa0ba61f00b11a0233e6eb9109ace2870 Mon Sep 17 00:00:00 2001 From: Maxim Kostovetski Date: Thu, 21 May 2020 13:37:21 -0700 Subject: [PATCH] Fix squash decoder option to squash only embedded fields Fixes #193 --- mapstructure.go | 4 +-- mapstructure_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/mapstructure.go b/mapstructure.go index 5e9408d3..bae0c39a 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -843,7 +843,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re keyName := f.Name // If Squash is set in the config, we squash the field down. - squash := d.config.Squash && v.Kind() == reflect.Struct + 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] == "-" { @@ -1187,7 +1187,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e fieldKind := fieldType.Type.Kind() // If "squash" is specified in the tag, we squash the field down. - squash := d.config.Squash && fieldKind == reflect.Struct + squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous remain := false // We always parse the tags cause we're looking for other tags too diff --git a/mapstructure_test.go b/mapstructure_test.go index 97c8e90a..7274f14f 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -61,6 +61,12 @@ type EmbeddedSquash struct { Vunique string } +type EmbeddedAndNamed struct { + Basic + Named Basic + Vunique string +} + type SliceAlias []string type EmbeddedSlice struct { @@ -612,9 +618,12 @@ func TestDecode_EmbeddedSquashConfig(t *testing.T) { input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", + "Named": map[string]interface{}{ + "vstring": "baz", + }, } - var result Embedded + var result EmbeddedAndNamed config := &DecoderConfig{ Squash: true, Result: &result, @@ -637,6 +646,66 @@ func TestDecode_EmbeddedSquashConfig(t *testing.T) { if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } + + if result.Named.Vstring != "baz" { + t.Errorf("Named.vstring value should be 'baz': %#v", result.Named.Vstring) + } +} + +func TestDecodeFrom_EmbeddedSquashConfig(t *testing.T) { + t.Parallel() + + input := EmbeddedAndNamed{ + Basic: Basic{Vstring: "foo"}, + Named: Basic{Vstring: "baz"}, + Vunique: "bar", + } + + result := map[string]interface{}{} + config := &DecoderConfig{ + Squash: true, + Result: &result, + } + decoder, err := NewDecoder(config) + if err != nil { + t.Fatalf("got an err: %s", err.Error()) + } + + err = decoder.Decode(input) + if err != nil { + t.Fatalf("got an err: %s", err.Error()) + } + + if _, ok := result["Basic"]; ok { + t.Error("basic should not be present in map") + } + + v, ok := result["Vstring"] + if !ok { + t.Error("vstring should be present in map") + } else if !reflect.DeepEqual(v, "foo") { + t.Errorf("vstring value should be 'foo': %#v", v) + } + + v, ok = result["Vunique"] + if !ok { + t.Error("vunique should be present in map") + } else if !reflect.DeepEqual(v, "bar") { + t.Errorf("vunique value should be 'bar': %#v", v) + } + + v, ok = result["Named"] + if !ok { + t.Error("Named should be present in map") + } else { + named := v.(map[string]interface{}) + v, ok := named["Vstring"] + if !ok { + t.Error("Named: vstring should be present in map") + } else if !reflect.DeepEqual(v, "baz") { + t.Errorf("Named: vstring should be 'baz': %#v", v) + } + } } func TestDecode_SquashOnNonStructType(t *testing.T) {