Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typed nil hook support #15

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion mapstructure.go
Expand Up @@ -1056,7 +1056,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (b
// pointer to be nil as well.
isNil := data == nil
if !isNil {
switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() {
switch v := reflect.ValueOf(data); v.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
Expand Down
81 changes: 81 additions & 0 deletions mapstructure_test.go
Expand Up @@ -2,9 +2,11 @@ package mapstructure

import (
"encoding/json"
"fmt"
"io"
"reflect"
"sort"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -2821,6 +2823,85 @@ func TestDecoder_IgnoreUntaggedFieldsWithStruct(t *testing.T) {
}
}

func TestTypedNilPostHooks(t *testing.T) {
type customType1 int
type customType2 float64
type configType struct {
C *customType1 `mapstructure:"c"`
A *customType2 `mapstructure:"a"`
}

for i, marshalledConfig := range []map[string]interface{}{
{
"c": (*customType1)(nil),
"a": (*customType2)(floatPtr(42.42)),
},
{
"c": "",
"a": "42.42",
},
} {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
actual := &configType{}
decoder, err := NewDecoder(&DecoderConfig{
Result: actual,
DecodeHook: ComposeDecodeHookFunc(
func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
var enum customType1
if f.Kind() != reflect.String || t != reflect.TypeOf(&enum) {
return data, nil
}
s := data.(string)
if s == "" {
// Returning an untyped nil here would cause a panic, as `from.Type()`
// is invalid for nil.
return (*customType1)(nil), nil
}
n, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return nil, err
}
enum = customType1(n)
return &enum, nil
},
func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
var enum customType2
if f.Kind() != reflect.String || t != reflect.TypeOf(&enum) {
return data, nil
}
s := data.(string)
if s == "" {
return (*customType2)(nil), nil
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
enum = customType2(n)
return &enum, nil
},
),
})
if err != nil {
t.Fatal(err)
}

if err := decoder.Decode(marshalledConfig); err != nil {
t.Fatal(err)
}

expected := &configType{
C: nil,
A: (*customType2)(floatPtr(42.42)),
}

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