Skip to content

fix: name map keys by value in error messages and metadata - #207

Open
yu2971512385-ui wants to merge 1 commit into
go-viper:mainfrom
yu2971512385-ui:fix/map-key-error-names
Open

yu2971512385-ui wants to merge 1 commit into
go-viper:mainfrom
yu2971512385-ui:fix/map-key-error-names

Conversation

@yu2971512385-ui

Copy link
Copy Markdown

Problem

Decoding any map that is not keyed by a string kind — including the map[any]any that gopkg.in/yaml.v2 and other decoders produce — reports every key as <interface {} Value>:

var result map[string]string
err := mapstructure.Decode(map[any]any{7: "foo"}, &result)
// '[<interface {} Value>]' expected type 'string', got unconvertible type 'int'
type S struct{ M map[int]string }

var md mapstructure.Metadata
d, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{Result: &S{}, Metadata: &md})
d.Decode(map[string]any{"m": map[any]any{7: []string{"boom"}}})

// err:      'M[<interface {} Value>]' expected type 'string', got unconvertible type '[]string'
// md.Keys:  [M[<interface {} Value>] M[<interface {} Value>] M]

The same decode against a map[string]any source names the key correctly (M[x]), so the diagnostics silently degrade exactly for the input shape that is most common in YAML-backed configuration — the caller is told a key failed but not which one.

Root cause

decodeMapFromMap builds the name with reflect.Value.String():

for _, k := range dataVal.MapKeys() {
    fieldName := name + "[" + k.String() + "]"

reflect.Value.String() is documented as the one getter that does not panic for the wrong kind: it returns the string only for Kind() == String and "<T Value>" otherwise. For map[any]any the key values have Kind() == Interface, and for map[int]T they have Kind() == Int.

Fix

Add a small mapKeyName helper that unwraps an interface key and formats the underlying value, and use it for the field name. String keys keep taking the existing fast path, so no existing name changes:

before: 'M[<interface {} Value>]' expected type 'string', got unconvertible type '[]string'
after:  'M[7]' expected type 'string', got unconvertible type '[]string'

Tests

  • TestDecodeMapKeyName — an int key that cannot be decoded into a string key must be named '[7]' in the error.
  • TestMetadata_MapKeys — an any-keyed source map must contribute Vmap[foo] / Vmap[baz] to Metadata.Keys.

Both fail without the change; go test ./... passes with it.

decodeMapFromMap built the per-key name with reflect.Value.String(),
which returns the key only for a string Kind and a placeholder for
everything else. Any map that is not keyed by a string kind - notably
the map[any]any that YAML decoders produce - therefore reported every
key as <interface {} Value>:

    'M[<interface {} Value>]' expected type 'string', got unconvertible type '[]string'

and Metadata.Keys collected the same placeholder instead of the keys.

Unwrap an interface key and format the underlying value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant