diff --git a/lib/decode/decode.go b/lib/decode/decode.go index 6730d47f8588..7a6534e64d9a 100644 --- a/lib/decode/decode.go +++ b/lib/decode/decode.go @@ -44,21 +44,28 @@ func HookTranslateKeys(_, to reflect.Type, data interface{}) (interface{}, error } rules := translationsForType(to) + // Avoid making a copy if there are no translation rules + if len(rules) == 0 { + return data, nil + } + result := make(map[string]interface{}, len(source)) for k, v := range source { lowerK := strings.ToLower(k) canonKey, ok := rules[lowerK] if !ok { + result[k] = v continue } - delete(source, k) // if there is a value for the canonical key then keep it - if _, ok := source[canonKey]; ok { + if canonValue, ok := source[canonKey]; ok { + // Assign the value for the case where canonKey == k + result[canonKey] = canonValue continue } - source[canonKey] = v + result[canonKey] = v } - return source, nil + return result, nil } // TODO: could be cached if it is too slow diff --git a/lib/decode/decode_test.go b/lib/decode/decode_test.go index 72e012b39c6a..1f2b5d35297a 100644 --- a/lib/decode/decode_test.go +++ b/lib/decode/decode_test.go @@ -178,6 +178,33 @@ func TestHookTranslateKeys_TargetStructHasPointerReceiver(t *testing.T) { require.Equal(t, expected, target, "decode metadata: %#v", md) } +func TestHookTranslateKeys_DoesNotModifySourceData(t *testing.T) { + raw := map[string]interface{}{ + "S": map[string]interface{}{ + "None": "no translation", + "OldOne": "value1", + "oldtwo": "value2", + }, + } + + cfg := Config{} + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: HookTranslateKeys, + Result: &cfg, + }) + require.NoError(t, err) + require.NoError(t, decoder.Decode(raw)) + + expected := map[string]interface{}{ + "S": map[string]interface{}{ + "None": "no translation", + "OldOne": "value1", + "oldtwo": "value2", + }, + } + require.Equal(t, raw, expected) +} + type translateExample struct { FieldDefaultCanonical string `alias:"first"` FieldWithMapstructureTag string `alias:"second" mapstructure:"field_with_mapstruct_tag"`