Repository: mitchellh/mapstructure
Pull request #185 (2020-04-27 19:56:31)

Fix an issues where pointers were causing a key to be recorded twice
The issue I was running into was that for pointer fields in a struct the key was being recorded twice in the `Metadata.Keys` list. There are some cases where `decodePtr` will recurse into another call to `decode` which will add the field. However the outer `decode` call was unconditionally adding the field as well.

The updates in this PR change the behavior to only add the pointer fields name to the list of keys if no recursion was performed.


Patch:
diff --git a/mapstructure.go b/mapstructure.go
index daea331..e053510 100644
--- a/mapstructure.go
+++ b/mapstructure.go
@@ -378,6 +378,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 
 	var err error
 	outputKind := getKind(outVal)
+	addMetaKey := true
 	switch outputKind {
 	case reflect.Bool:
 		err = d.decodeBool(name, input, outVal)
@@ -396,7 +397,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 	case reflect.Map:
 		err = d.decodeMap(name, input, outVal)
 	case reflect.Ptr:
-		err = d.decodePtr(name, input, outVal)
+		addMetaKey, err = d.decodePtr(name, input, outVal)
 	case reflect.Slice:
 		err = d.decodeSlice(name, input, outVal)
 	case reflect.Array:
@@ -410,7 +411,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 
 	// If we reached here, then we successfully decoded SOMETHING, so
 	// mark the key as used if we're tracking metainput.
-	if d.config.Metadata != nil && name != "" {
+	if addMetaKey && d.config.Metadata != nil && name != "" {
 		d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
 	}
 
@@ -861,7 +862,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
 	return nil
 }
 
-func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
+func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
 	// If the input data is nil, then we want to just set the output
 	// pointer to be nil as well.
 	isNil := data == nil
@@ -882,7 +883,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
 			val.Set(nilValue)
 		}
 
-		return nil
+		return true, nil
 	}
 
 	// Create an element of the concrete (non pointer) type and decode
@@ -896,16 +897,16 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
 		}
 
 		if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
-			return err
+			return false, err
 		}
 
 		val.Set(realVal)
 	} else {
 		if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
-			return err
+			return false, err
 		}
 	}
-	return nil
+	return false, nil
 }
 
 func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {


Test patch:
diff --git a/mapstructure_test.go b/mapstructure_test.go
index 927f076..2aaed95 100644
--- a/mapstructure_test.go
+++ b/mapstructure_test.go
@@ -1939,6 +1939,11 @@ func TestDecodeMetadata(t *testing.T) {
 func TestMetadata(t *testing.T) {
 	t.Parallel()
 
+	type testResult struct {
+		Vfoo string
+		Vbar BasicPointer
+	}
+
 	input := map[string]interface{}{
 		"vfoo": "foo",
 		"vbar": map[string]interface{}{
@@ -1951,7 +1956,7 @@ func TestMetadata(t *testing.T) {
 	}
 
 	var md Metadata
-	var result Nested
+	var result testResult
 	config := &DecoderConfig{
 		Metadata: &md,
 		Result:   &result,