diff --git a/mapstructure.go b/mapstructure.go index 05bc140..76e8f8d 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -442,22 +442,33 @@ func (d *Decoder) Decode(input interface{}) error { return err } -// Decodes an unknown data type into a specific reflection value. -func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { - var inputVal reflect.Value - if input != nil { - inputVal = reflect.ValueOf(input) - - // We need to check here if input is a typed nil. Typed nils won't - // match the "input == nil" below so we check that here. - if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { - input = nil - } +// A comparison input == nil will fail if input is a typed nil. +// This function converts a typed nil to an actual, untyped nil. +func toRealNil(input interface{}) interface{} { + if input == nil { + return nil } + val := reflect.ValueOf(input) + k := val.Kind() + if (k == reflect.Ptr || + k == reflect.Interface || + k == reflect.Map || + k == reflect.Slice || + k == reflect.Array) && val.IsNil() { + return nil + } + return input +} - decodeNil := d.config.DecodeNil && d.config.DecodeHook != nil - - if input == nil { +// Decodes an unknown data type into a specific reflection value. +func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error { + var ( + inputVal = reflect.ValueOf(input) + outputKind = getKind(outVal) + decodeNil = d.config.DecodeNil && d.cachedDecodeHook != nil + ) + input = toRealNil(input) + if input == nil || !inputVal.IsValid() { // If the data is nil, then we don't set anything, unless ZeroFields is set // to true. if d.config.ZeroFields { @@ -467,40 +478,37 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } } - if !decodeNil { return nil } } - if !inputVal.IsValid() { - if !decodeNil { - // If the input value is invalid, then we just set the value - // to be the zero value. - outVal.Set(reflect.Zero(outVal.Type())) - if d.config.Metadata != nil && name != "" { - d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) - } - return nil - } - - // If we get here, we have an untyped nil so the type of the input is assumed. - // We do this because all subsequent code requires a valid value for inputVal. - var mapVal map[string]interface{} - inputVal = reflect.MakeMap(reflect.TypeOf(mapVal)) - } - if d.cachedDecodeHook != nil { // We have a DecodeHook, so let's pre-process the input. + if !inputVal.IsValid() { + // Hooks need a valid inputVal, so reset it to zero value of outVal type. + switch outputKind { + case reflect.Struct, reflect.Map: + var mapVal map[string]interface{} + inputVal = reflect.ValueOf(mapVal) + case reflect.Slice, reflect.Array: + var sliceVal []interface{} + inputVal = reflect.ValueOf(sliceVal) + default: + inputVal = reflect.Zero(outVal.Type()) + } + } var err error input, err = d.cachedDecodeHook(inputVal, outVal) if err != nil { return fmt.Errorf("error decoding '%s': %w", name, err) } } + if toRealNil(input) == nil { + return nil + } var err error - outputKind := getKind(outVal) addMetaKey := true switch outputKind { case reflect.Bool: