Skip to content

Commit cd1c879

Browse files
committed
Pass appropriate empty Value to hooks
Signed-off-by: Yuri Shkuro <[email protected]>
1 parent abbd7b4 commit cd1c879

File tree

1 file changed

+41
-33
lines changed

1 file changed

+41
-33
lines changed

mapstructure.go

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -442,22 +442,33 @@ func (d *Decoder) Decode(input interface{}) error {
442442
return err
443443
}
444444

445-
// Decodes an unknown data type into a specific reflection value.
446-
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
447-
var inputVal reflect.Value
448-
if input != nil {
449-
inputVal = reflect.ValueOf(input)
450-
451-
// We need to check here if input is a typed nil. Typed nils won't
452-
// match the "input == nil" below so we check that here.
453-
if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
454-
input = nil
455-
}
445+
// A comparison input == nil will fail if input is a typed nil.
446+
// This function converts a typed nil to an actual, untyped nil.
447+
func toRealNil(input interface{}) interface{} {
448+
if input == nil {
449+
return nil
456450
}
451+
val := reflect.ValueOf(input)
452+
k := val.Kind()
453+
if (k == reflect.Ptr ||
454+
k == reflect.Interface ||
455+
k == reflect.Map ||
456+
k == reflect.Slice ||
457+
k == reflect.Array) && val.IsNil() {
458+
return nil
459+
}
460+
return input
461+
}
457462

458-
decodeNil := d.config.DecodeNil && d.config.DecodeHook != nil
459-
460-
if input == nil {
463+
// Decodes an unknown data type into a specific reflection value.
464+
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
465+
var (
466+
inputVal = reflect.ValueOf(input)
467+
outputKind = getKind(outVal)
468+
decodeNil = d.config.DecodeNil && d.cachedDecodeHook != nil
469+
)
470+
input = toRealNil(input)
471+
if input == nil || !inputVal.IsValid() {
461472
// If the data is nil, then we don't set anything, unless ZeroFields is set
462473
// to true.
463474
if d.config.ZeroFields {
@@ -467,40 +478,37 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
467478
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
468479
}
469480
}
470-
471481
if !decodeNil {
472482
return nil
473483
}
474484
}
475485

476-
if !inputVal.IsValid() {
477-
if !decodeNil {
478-
// If the input value is invalid, then we just set the value
479-
// to be the zero value.
480-
outVal.Set(reflect.Zero(outVal.Type()))
481-
if d.config.Metadata != nil && name != "" {
482-
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
483-
}
484-
return nil
485-
}
486-
487-
// If we get here, we have an untyped nil so the type of the input is assumed.
488-
// We do this because all subsequent code requires a valid value for inputVal.
489-
var mapVal map[string]interface{}
490-
inputVal = reflect.MakeMap(reflect.TypeOf(mapVal))
491-
}
492-
493486
if d.cachedDecodeHook != nil {
494487
// We have a DecodeHook, so let's pre-process the input.
488+
if !inputVal.IsValid() {
489+
// Hooks need a valid inputVal, so reset it to zero value of outVal type.
490+
switch outputKind {
491+
case reflect.Struct, reflect.Map:
492+
var mapVal map[string]interface{}
493+
inputVal = reflect.ValueOf(mapVal)
494+
case reflect.Slice, reflect.Array:
495+
var sliceVal []interface{}
496+
inputVal = reflect.ValueOf(sliceVal)
497+
default:
498+
inputVal = reflect.Zero(outVal.Type())
499+
}
500+
}
495501
var err error
496502
input, err = d.cachedDecodeHook(inputVal, outVal)
497503
if err != nil {
498504
return fmt.Errorf("error decoding '%s': %w", name, err)
499505
}
500506
}
507+
if toRealNil(input) == nil {
508+
return nil
509+
}
501510

502511
var err error
503-
outputKind := getKind(outVal)
504512
addMetaKey := true
505513
switch outputKind {
506514
case reflect.Bool:

0 commit comments

Comments
 (0)