Metadata can track used keys and unused keys
diff --git a/mapstructure.go b/mapstructure.go index d2cd7a3..3a01f0e 100644 --- a/mapstructure.go +++ b/mapstructure.go
@@ -49,7 +49,7 @@ func Decode(m interface{}, rawVal interface{}) error { config := &DecoderConfig{ Metadata: nil, - Result: rawVal, + Result: rawVal, } decoder, err := NewDecoder(config) @@ -106,27 +106,36 @@ k = reflect.Uint } + var err error switch k { case reflect.Bool: fallthrough case reflect.Interface: fallthrough case reflect.String: - return d.decodeBasic(name, data, val) + err = d.decodeBasic(name, data, val) case reflect.Int: fallthrough case reflect.Uint: - return d.decodeInt(name, data, val) + err = d.decodeInt(name, data, val) case reflect.Struct: - return d.decodeStruct(name, data, val) + err = d.decodeStruct(name, data, val) case reflect.Map: - return d.decodeMap(name, data, val) + err = d.decodeMap(name, data, val) case reflect.Slice: - return d.decodeSlice(name, data, val) + err = d.decodeSlice(name, data, val) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, k) } - // If we reached this point then we weren't able to decode it - return fmt.Errorf("%s: unsupported type: %s", name, k) + // If we reached here, then we successfully decoded SOMETHING, so + // mark the key as used if we're tracking metadata. + if d.config.Metadata != nil && name != "" { + d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) + } + + return err } // This decodes a basic type (bool, int, string, etc.) and sets the @@ -294,8 +303,14 @@ name, dataValType.Key().Kind()) } - errors := make([]string, 0) + dataValKeys := make(map[reflect.Value]struct{}) + dataValKeysUnused := make(map[interface{}]struct{}) + for _, dataValKey := range dataVal.MapKeys() { + dataValKeys[dataValKey] = struct{}{} + dataValKeysUnused[dataValKey.Interface()] = struct{}{} + } + errors := make([]string, 0) valType := val.Type() for i := 0; i < valType.NumField(); i++ { fieldType := valType.Field(i) @@ -306,15 +321,17 @@ fieldName = tagValue } - rawMapVal := dataVal.MapIndex(reflect.ValueOf(fieldName)) + rawMapKey := reflect.ValueOf(fieldName) + rawMapVal := dataVal.MapIndex(rawMapKey) if !rawMapVal.IsValid() { // Do a slower search by iterating over each key and // doing case-insensitive search. - for _, dataKeyVal := range dataVal.MapKeys() { - mK := dataKeyVal.Interface().(string) + for dataValKey, _ := range dataValKeys { + mK := dataValKey.Interface().(string) if strings.EqualFold(mK, fieldName) { - rawMapVal = dataVal.MapIndex(dataKeyVal) + rawMapKey = dataValKey + rawMapVal = dataVal.MapIndex(dataValKey) break } } @@ -326,6 +343,9 @@ } } + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + field := val.Field(i) if !field.IsValid() { // This should never happen @@ -353,5 +373,17 @@ return &Error{errors} } + // Add the unused keys to the list of unused keys if we're tracking metadata + if d.config.Metadata != nil { + for rawKey, _ := range dataValKeysUnused { + key := rawKey.(string) + if name != "" { + key = fmt.Sprintf("%s.%s", name, key) + } + + d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) + } + } + return nil }
diff --git a/mapstructure_test.go b/mapstructure_test.go index 4d664ed..cc1f249 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go
@@ -1,6 +1,9 @@ package mapstructure -import "testing" +import ( + "reflect" + "testing" +) type Basic struct { Vstring string @@ -343,6 +346,47 @@ } } +func TestMetadata(t *testing.T) { + t.Parallel() + + input := map[string]interface{}{ + "vfoo": "foo", + "vbar": map[string]interface{}{ + "vstring": "foo", + "Vuint": 42, + "foo": "bar", + }, + "bar": "nil", + } + + var md Metadata + var result Nested + config := &DecoderConfig{ + Metadata: &md, + Result: &result, + } + + decoder, err := NewDecoder(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = decoder.Decode(input) + if err != nil { + t.Fatalf("err: %s", err.Error()) + } + + expectedKeys := []string{"Vfoo", "Vbar.Vstring", "Vbar.Vuint", "Vbar"} + if !reflect.DeepEqual(md.Keys, expectedKeys) { + t.Fatalf("bad keys: %#v", md.Keys) + } + + expectedUnused := []string{"Vbar.foo", "bar"} + if !reflect.DeepEqual(md.Unused, expectedUnused) { + t.Fatalf("bad unused: %#v", md.Unused) + } +} + func TestNonPtrValue(t *testing.T) { t.Parallel()