Skip to content

Commit

Permalink
Merge pull request #250 from abeMedia/feat/custom-name-matchers
Browse files Browse the repository at this point in the history
Support custom name matchers
  • Loading branch information
mitchellh committed Sep 14, 2021
2 parents 381a76b + fd87e0d commit 1b7c3d8
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 1 deletion.
13 changes: 12 additions & 1 deletion mapstructure.go
Expand Up @@ -258,6 +258,10 @@ type DecoderConfig struct {
// The tag name that mapstructure reads for field names. This
// defaults to "mapstructure"
TagName string

// MatchName is the function used to match the map key to the struct
// field name or tag. Defaults to `strings.EqualFold`.
MatchName func(mapKey, fieldName string) bool
}

// A Decoder takes a raw interface value and turns it into structured
Expand Down Expand Up @@ -1340,7 +1344,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
continue
}

if strings.EqualFold(mK, fieldName) {
if d.matchName(mK, fieldName) {
rawMapKey = dataValKey
rawMapVal = dataVal.MapIndex(dataValKey)
break
Expand Down Expand Up @@ -1428,6 +1432,13 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
return nil
}

func (d *Decoder) matchName(mapKey, fieldName string) bool {
if d.config.MatchName != nil {
return d.config.MatchName(mapKey, fieldName)
}
return strings.EqualFold(mapKey, fieldName)
}

func isEmptyValue(v reflect.Value) bool {
switch getKind(v) {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
Expand Down
43 changes: 43 additions & 0 deletions mapstructure_test.go
Expand Up @@ -2431,6 +2431,49 @@ func TestDecode_mapToStruct(t *testing.T) {
}
}

func TestDecoder_MatchName(t *testing.T) {
t.Parallel()

type Target struct {
FirstMatch string `mapstructure:"first_match"`
SecondMatch string
NoMatch string `mapstructure:"no_match"`
}

input := map[string]interface{}{
"first_match": "foo",
"SecondMatch": "bar",
"NO_MATCH": "baz",
}

expected := Target{
FirstMatch: "foo",
SecondMatch: "bar",
}

var actual Target
config := &DecoderConfig{
Result: &actual,
MatchName: func(mapKey, fieldName string) bool {
return mapKey == fieldName
},
}

decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("err: %s", err)
}

err = decoder.Decode(input)
if err != nil {
t.Fatalf("err: %s", err)
}

if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
}
}

func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
var result Slice
err := Decode(input, &result)
Expand Down

0 comments on commit 1b7c3d8

Please sign in to comment.