support squashing embedded structs
diff --git a/mapstructure.go b/mapstructure.go index 2941a51..70da05b 100644 --- a/mapstructure.go +++ b/mapstructure.go
@@ -333,6 +333,29 @@ fieldType := valType.Field(i) fieldName := fieldType.Name + if fieldType.Anonymous { + // We have an embedded field. We "squash" the fields down if + // specified in the tag. + squash := false + tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + + if squash { + inner := val.FieldByName(fieldName) + err := d.decodeStruct(name, data, inner) + if err != nil { + errors = appendErrors(errors, err) + } + + continue + } + } + tagValue := fieldType.Tag.Get(d.config.TagName) if tagValue != "" { fieldName = tagValue
diff --git a/mapstructure_examples_test.go b/mapstructure_examples_test.go index 5faae12..d7f3d00 100644 --- a/mapstructure_examples_test.go +++ b/mapstructure_examples_test.go
@@ -71,16 +71,16 @@ func ExampleDecode_metadata() { type Person struct { - Name string - Age int + Name string + Age int } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ - "name": "Mitchell", - "age": 91, + "name": "Mitchell", + "age": 91, "email": "foo@bar.com", } @@ -91,7 +91,7 @@ var result Person config := &DecoderConfig{ Metadata: &md, - Result: &result, + Result: &result, } decoder, err := NewDecoder(config)
diff --git a/mapstructure_test.go b/mapstructure_test.go index b3e1363..12e31c2 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go
@@ -15,6 +15,16 @@ Vdata interface{} } +type Embedded struct { + Basic + Vunique string +} + +type EmbeddedSquash struct { + Basic `mapstructure:",squash"` + Vunique string +} + type Map struct { Vfoo string Vother map[string]string @@ -104,6 +114,55 @@ } } +func TestDecode_Embedded(t *testing.T) { + t.Parallel() + + input := map[string]interface{}{ + "vstring": "foo", + "Basic": map[string]interface{}{ + "vstring": "innerfoo", + }, + "vunique": "bar", + } + + var result Embedded + err := Decode(input, &result) + if err != nil { + t.Fatalf("got an err: %s", err.Error()) + } + + if result.Vstring != "innerfoo" { + t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring) + } + + if result.Vunique != "bar" { + t.Errorf("vunique value should be 'bar': %#v", result.Vunique) + } +} + +func TestDecode_EmbeddedSquash(t *testing.T) { + t.Parallel() + + input := map[string]interface{}{ + "vstring": "foo", + "vunique": "bar", + } + + var result EmbeddedSquash + err := Decode(input, &result) + if err != nil { + t.Fatalf("got an err: %s", err.Error()) + } + + if result.Vstring != "foo" { + t.Errorf("vstring value should be 'foo': %#v", result.Vstring) + } + + if result.Vunique != "bar" { + t.Errorf("vunique value should be 'bar': %#v", result.Vunique) + } +} + func TestDecode_NonStruct(t *testing.T) { t.Parallel() @@ -128,13 +187,13 @@ input := map[string]interface{}{ "vstring": "hello", - "foo": "bar", + "foo": "bar", } var result Basic config := &DecoderConfig{ ErrorUnused: true, - Result: &result, + Result: &result, } decoder, err := NewDecoder(config)