From 2ac22fe3e841997f6922b6cd971e7a60670f92ff Mon Sep 17 00:00:00 2001 From: Team Humaki Date: Thu, 17 Sep 2026 22:24:32 -0700 Subject: [PATCH] decode: record Metadata.Keys for struct-to-map fields decodeMapFromStruct copied exported fields into the result map without recording them, so DecodeMetadata left Keys empty. Fixes #127 --- mapstructure.go | 15 +++++++++++++++ mapstructure_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/mapstructure.go b/mapstructure.go index 9087fd96..1940f8dd 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -1203,6 +1203,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re ptr := v.MapRange() for ptr.Next() { valMap.SetMapIndex(ptr.Key(), ptr.Value()) + if k, ok := ptr.Key().Interface().(string); ok { + d.appendMetaKey(name, k) + } } continue } @@ -1293,6 +1296,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re default: valMap.SetMapIndex(reflect.ValueOf(keyName), v) + d.appendMetaKey(name, keyName) } } @@ -1801,6 +1805,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e return nil } +// appendMetaKey records a successfully decoded key when metadata is enabled. +func (d *Decoder) appendMetaKey(name, key string) { + if d.config.Metadata == nil || key == "" { + return + } + if name != "" { + key = name + "." + key + } + d.config.Metadata.Keys = append(d.config.Metadata.Keys, key) +} + func isEmptyValue(v reflect.Value) bool { switch getKind(v) { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: diff --git a/mapstructure_test.go b/mapstructure_test.go index baf40dfe..5e3daf38 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2737,6 +2737,50 @@ func TestDecodeMetadata(t *testing.T) { } } +func TestDecodeMetadata_StructToMap(t *testing.T) { + t.Parallel() + + type ExampleStruct struct { + ID string `mapstructure:"id"` + Name string `mapstructure:"name"` + Description string `mapstructure:"description"` + Hidden string `mapstructure:"-"` + unexported string + } + + input := &ExampleStruct{ + ID: "123", + Name: "test", + Description: "desc", + Hidden: "hidden", + unexported: "unexported value", + } + + var resultMap map[string]any + var md Metadata + if err := DecodeMetadata(input, &resultMap, &md); err != nil { + t.Fatalf("err: %s", err) + } + + wantMap := map[string]any{ + "id": "123", + "name": "test", + "description": "desc", + } + if !reflect.DeepEqual(resultMap, wantMap) { + t.Fatalf("bad result: %#v", resultMap) + } + + wantKeys := []string{"description", "id", "name"} + sort.Strings(md.Keys) + if !reflect.DeepEqual(md.Keys, wantKeys) { + t.Fatalf("bad keys: %#v", md.Keys) + } + if len(md.Unused) != 0 { + t.Fatalf("bad unused: %#v", md.Unused) + } +} + func TestMetadata(t *testing.T) { t.Parallel()