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

Nils should be accepted as zero values of any type ONLY WHEN WeaklyTypedInput=true #157

Open
wants to merge 3 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
6 changes: 6 additions & 0 deletions mapstructure.go
Expand Up @@ -77,6 +77,7 @@ type DecoderConfig struct {
// - single values are converted to slices if required. Each
// element is weakly decoded. For example: "4" can become []int{4}
// if the target type is an int slice.
// - nils are accepted as zero values of any type
//
WeaklyTypedInput bool

Expand Down Expand Up @@ -236,6 +237,11 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
}

if input == nil {
// We convert nils into zero values only if WeaklyTypedInput is set to true
if !d.config.WeaklyTypedInput && outVal.Kind() != reflect.Ptr && outVal.Kind() != reflect.Interface {
return fmt.Errorf("'%s' should not be null (expected type: %s)", name, outVal.Kind())
}

// If the data is nil, then we don't set anything, unless ZeroFields is set
// to true.
if d.config.ZeroFields {
Expand Down
7 changes: 4 additions & 3 deletions mapstructure_bugs_test.go
Expand Up @@ -110,9 +110,10 @@ func TestDecode_NilValue(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config := &DecoderConfig{
Metadata: new(Metadata),
Result: tc.target,
ZeroFields: true,
Metadata: new(Metadata),
Result: tc.target,
ZeroFields: true,
WeaklyTypedInput: true,
}

decoder, err := NewDecoder(config)
Expand Down
157 changes: 145 additions & 12 deletions mapstructure_test.go
Expand Up @@ -239,6 +239,113 @@ func TestBasicTypes(t *testing.T) {
}
}

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

input := map[string]interface{}{
"vstring": nil,
"vint": nil,
"Vuint": nil,
"vbool": nil,
"Vfloat": nil,
"vsilent": nil,
"vdata": nil,
"vjsonInt": nil,
"vjsonFloat": nil,
"vjsonNumber": nil,
}

var result Basic
err := Decode(input, &result)

if err == nil {
t.Errorf("should be an error")
t.FailNow()
}
if !strings.Contains(err.Error(), "'Vstring' should not be null (expected type: string)") {
t.Errorf("no error for 'Vstring' in %#v", err)
}
if !strings.Contains(err.Error(), "'Vint' should not be null (expected type: int)") {
t.Errorf("no error for 'Vint' in %#v", err)
}
if !strings.Contains(err.Error(), "'Vbool' should not be null (expected type: bool)") {
t.Errorf("no error for 'Vbool' in %#v", err)
}
if !strings.Contains(err.Error(), "'Vfloat' should not be null (expected type: float64)") {
t.Errorf("no error for 'Vfloat' in %#v", err)
}
if strings.Contains(err.Error(), "Vdata") {
t.Errorf("got error for 'Vdata' in %#v", err)
}
if !strings.Contains(err.Error(), "'VjsonInt' should not be null (expected type: int)") {
t.Errorf("no error for 'VjsonInt' in %#v", err)
}
if !strings.Contains(err.Error(), "'VjsonFloat' should not be null (expected type: float64)") {
t.Errorf("no error for 'VjsonFloat' in %#v", err)
}
if !strings.Contains(err.Error(), "'VjsonNumber' should not be null (expected type: string)") {
t.Errorf("no error for 'VjsonNumber' in %#v", err)
}
}

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

input := map[string]interface{}{
"vstring": nil,
"vint": nil,
"Vuint": nil,
"vbool": nil,
"Vfloat": nil,
"vsilent": nil,
"vdata": nil,
"vjsonInt": nil,
"vjsonFloat": nil,
"vjsonNumber": nil,
}

var result Basic
err := WeakDecode(input, &result)

if err != nil {
t.Fatalf("got an err: %s", err)
}

expected := Basic{}
if result != expected {
t.Errorf("result is not a zero structure: %#v", result)
}
}

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

input := map[string]interface{}{
"vstring": nil,
"vint": nil,
"Vuint": nil,
"vbool": nil,
"Vfloat": nil,
"vsilent": nil,
"vdata": nil,
"vjsonInt": nil,
"vjsonFloat": nil,
"vjsonNumber": nil,
}

var result BasicPointer
err := Decode(input, &result)

if err != nil {
t.Fatalf("got an err: %s", err)
}

expected := BasicPointer{}
if result != expected {
t.Errorf("result is not a zero structure: %#v", result)
}
}

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

Expand Down Expand Up @@ -609,6 +716,24 @@ func TestDecode_Nil(t *testing.T) {
}

err := Decode(input, &result)
if err == nil {
t.Fatalf("should be an error")
}

if !strings.Contains(err.Error(), "'' should not be null (expected type: struct)") {
t.Fatalf("unexpected error message: %s", err)
}
}

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

var input interface{}
result := Basic{
Vstring: "foo",
}

err := WeakDecode(input, &result)
if err != nil {
t.Fatalf("err: %s", err)
}
Expand Down Expand Up @@ -1469,21 +1594,29 @@ func TestDecodeTable(t *testing.T) {
{
"basic pointer to non-pointer",
&BasicPointer{
Vstring: stringPtr("vstring"),
Vint: intPtr(2),
Vuint: uintPtr(3),
Vbool: boolPtr(true),
Vfloat: floatPtr(4.56),
Vdata: interfacePtr([]byte("data")),
Vstring: stringPtr("vstring"),
Vint: intPtr(2),
Vuint: uintPtr(3),
Vbool: boolPtr(true),
Vfloat: floatPtr(4.56),
Vdata: interfacePtr([]byte("data")),
Vextra: stringPtr("extra"),
VjsonFloat: floatPtr(1.1),
VjsonInt: intPtr(5),
VjsonNumber: func() *json.Number { n := json.Number(123); return &n }(),
},
&Basic{},
&Basic{
Vstring: "vstring",
Vint: 2,
Vuint: 3,
Vbool: true,
Vfloat: 4.56,
Vdata: []byte("data"),
Vstring: "vstring",
Vint: 2,
Vuint: 3,
Vbool: true,
Vfloat: 4.56,
Vdata: []byte("data"),
Vextra: "extra",
VjsonFloat: 1.1,
VjsonInt: 5,
VjsonNumber: json.Number(123),
},
false,
},
Expand Down