Repository: mitchellh/mapstructure
Pull request #150 (2018-12-16 14:54:37)

Do not delete key from unused map if field value cannot be set
Track unusable keys in case the value cannot be set. This helps troubleshoot issues in case a struct was unexported. It took me over a hour to find such an issue once, I would like to avoid this in the future.

mitchellh: Hi, its been over a year so I apologize but if you're interested in this I'd love to see a test case added here. 

Patch:
diff --git a/mapstructure.go b/mapstructure.go
index 968abf9..daea331 100644
--- a/mapstructure.go
+++ b/mapstructure.go
@@ -1219,9 +1219,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
 			}
 		}
 
-		// Delete the key we're using from the unused map so we stop tracking
-		delete(dataValKeysUnused, rawMapKey.Interface())
-
 		if !fieldValue.IsValid() {
 			// This should never happen
 			panic("field is not valid")
@@ -1233,6 +1230,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
 			continue
 		}
 
+		// Delete the key we're using from the unused map so we stop tracking
+		delete(dataValKeysUnused, rawMapKey.Interface())
+
 		// If the name is empty string, then we're at the root, and we
 		// don't dot-join the fields.
 		if name != "" {


Test patch:
diff --git a/mapstructure_test.go b/mapstructure_test.go
index dd131dc..ea58f6b 100644
--- a/mapstructure_test.go
+++ b/mapstructure_test.go
@@ -1019,6 +1019,31 @@ func TestDecoder_ErrorUnused(t *testing.T) {
 	}
 }
 
+func TestDecoder_ErrorUnused_NotSetable(t *testing.T) {
+	t.Parallel()
+
+	// lowercase vsilent is unexported and cannot be set
+	input := map[string]interface{}{
+		"vsilent": "false",
+	}
+
+	var result Basic
+	config := &DecoderConfig{
+		ErrorUnused: true,
+		Result:      &result,
+	}
+
+	decoder, err := NewDecoder(config)
+	if err != nil {
+		t.Fatalf("err: %s", err)
+	}
+
+	err = decoder.Decode(input)
+	if err == nil {
+		t.Fatal("expected error")
+	}
+}
+
 func TestMap(t *testing.T) {
 	t.Parallel()
 
@@ -1884,6 +1909,7 @@ func TestDecodeMetadata(t *testing.T) {
 		"vbar": map[string]interface{}{
 			"vstring": "foo",
 			"Vuint":   42,
+			"vsilent": "false",
 			"foo":     "bar",
 		},
 		"bar": "nil",
@@ -1903,7 +1929,7 @@ func TestDecodeMetadata(t *testing.T) {
 		t.Fatalf("bad keys: %#v", md.Keys)
 	}
 
-	expectedUnused := []string{"Vbar.foo", "bar"}
+	expectedUnused := []string{"Vbar.vsilent", "Vbar.foo", "bar"}
 	if !reflect.DeepEqual(md.Unused, expectedUnused) {
 		t.Fatalf("bad unused: %#v", md.Unused)
 	}
@@ -1917,6 +1943,7 @@ func TestMetadata(t *testing.T) {
 		"vbar": map[string]interface{}{
 			"vstring": "foo",
 			"Vuint":   42,
+			"vsilent": "false",
 			"foo":     "bar",
 		},
 		"bar": "nil",
@@ -1945,7 +1972,7 @@ func TestMetadata(t *testing.T) {
 		t.Fatalf("bad keys: %#v", md.Keys)
 	}
 
-	expectedUnused := []string{"Vbar.foo", "bar"}
+	expectedUnused := []string{"Vbar.vsilent", "Vbar.foo", "bar"}
 	if !reflect.DeepEqual(md.Unused, expectedUnused) {
 		t.Fatalf("bad unused: %#v", md.Unused)
 	}
@@ -2053,15 +2080,17 @@ func TestWeakDecodeMetadata(t *testing.T) {
 	t.Parallel()
 
 	input := map[string]interface{}{
-		"foo":    "4",
-		"bar":    "value",
-		"unused": "value",
+		"foo":        "4",
+		"bar":        "value",
+		"unused":     "value",
+		"unexported": "value",
 	}
 
 	var md Metadata
 	var result struct {
-		Foo int
-		Bar string
+		Foo        int
+		Bar        string
+		unexported string
 	}
 
 	if err := WeakDecodeMetadata(input, &result, &md); err != nil {
@@ -2080,7 +2109,7 @@ func TestWeakDecodeMetadata(t *testing.T) {
 		t.Fatalf("bad keys: %#v", md.Keys)
 	}
 
-	expectedUnused := []string{"unused"}
+	expectedUnused := []string{"unused", "unexported"}
 	if !reflect.DeepEqual(md.Unused, expectedUnused) {
 		t.Fatalf("bad unused: %#v", md.Unused)
 	}