Skip to content

Commit 4bf87ce

Browse files
author
Jimmy Spagnola
committed
feature: Add map field name to convert structs dynamically instead of individually with a tag.
1 parent af2cf2a commit 4bf87ce

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

mapstructure.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ type DecoderConfig struct {
316316
// DecodeNil, if set to true, will cause the DecodeHook (if present) to run
317317
// even if the input is nil. This can be used to provide default values.
318318
DecodeNil bool
319+
320+
// MapFieldName is the function used to convert the struct field name to the map's key name.
321+
// This can be used to support snake casing, etc., without explicitly mapping each name.
322+
MapFieldName func(string) string
319323
}
320324

321325
// A Decoder takes a raw interface value and turns it into structured
@@ -452,6 +456,12 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
452456
config.MatchName = strings.EqualFold
453457
}
454458

459+
if config.MapFieldName == nil {
460+
config.MapFieldName = func(s string) string {
461+
return s
462+
}
463+
}
464+
455465
result := &Decoder{
456466
config: config,
457467
}
@@ -1068,7 +1078,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
10681078
}
10691079

10701080
tagValue := f.Tag.Get(d.config.TagName)
1071-
keyName := f.Name
1081+
keyName := d.config.MapFieldName(f.Name)
10721082

10731083
if tagValue == "" && d.config.IgnoreUntaggedFields {
10741084
continue

mapstructure_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3681,3 +3681,43 @@ func uintPtr(v uint) *uint { return &v }
36813681
func boolPtr(v bool) *bool { return &v }
36823682
func floatPtr(v float64) *float64 { return &v }
36833683
func interfacePtr(v any) *any { return &v }
3684+
3685+
type TestMapFieldName struct {
3686+
HostName string
3687+
Username string
3688+
}
3689+
3690+
func TestDecoder_MapFieldName(t *testing.T) {
3691+
var structKeys map[string]any
3692+
3693+
decoder, err := NewDecoder(&DecoderConfig{
3694+
ErrorUnused: true, // Enable error on unused keys
3695+
Result: &structKeys,
3696+
MapFieldName: func(s string) string {
3697+
if s == "HostName" {
3698+
return "host_name"
3699+
}
3700+
return s
3701+
},
3702+
})
3703+
if err != nil {
3704+
t.Fatalf("err: %s", err)
3705+
}
3706+
3707+
var input TestMapFieldName
3708+
3709+
err = decoder.Decode(&input)
3710+
if err != nil {
3711+
t.Fatalf("err: %s", err)
3712+
}
3713+
3714+
_, ok := structKeys["host_name"]
3715+
if !ok {
3716+
t.Fatal("expected host_name to exist")
3717+
}
3718+
3719+
_, ok = structKeys["Username"]
3720+
if !ok {
3721+
t.Fatal("expected Username to exist")
3722+
}
3723+
}

0 commit comments

Comments
 (0)