Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mapstructure.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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