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

decodeMapFromSlice should ignore ZeroFields when not first element #276

Open
wants to merge 1 commit 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
30 changes: 21 additions & 9 deletions mapstructure.go
Expand Up @@ -395,11 +395,11 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
func (d *Decoder) Decode(input interface{}) error {
return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
return d.decode("", input, reflect.ValueOf(d.config.Result).Elem(), false)
}

// Decodes an unknown data type into a specific reflection value.
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value, shouldIgnoreZeroFields ...bool) error {
var inputVal reflect.Value
if input != nil {
inputVal = reflect.ValueOf(input)
Expand All @@ -414,7 +414,11 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
if input == nil {
// If the data is nil, then we don't set anything, unless ZeroFields is set
// to true.
if d.config.ZeroFields {
ignoreZeroFields := false
if len(shouldIgnoreZeroFields) > 0 {
ignoreZeroFields = shouldIgnoreZeroFields[0]
}
if !ignoreZeroFields && d.config.ZeroFields {
outVal.Set(reflect.Zero(outVal.Type()))

if d.config.Metadata != nil && name != "" {
Expand Down Expand Up @@ -462,7 +466,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
case reflect.Struct:
err = d.decodeStruct(name, input, outVal)
case reflect.Map:
err = d.decodeMap(name, input, outVal)
err = d.decodeMap(name, input, outVal, shouldIgnoreZeroFields...)
case reflect.Ptr:
addMetaKey, err = d.decodePtr(name, input, outVal)
case reflect.Slice:
Expand Down Expand Up @@ -777,16 +781,21 @@ func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value)
return nil
}

func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value, shouldIgnoreZeroFields ...bool) error {
valType := val.Type()
valKeyType := valType.Key()
valElemType := valType.Elem()

// By default we overwrite keys in the current map
valMap := val

ignoreZeroFields := false
if len(shouldIgnoreZeroFields) > 0 {
ignoreZeroFields = shouldIgnoreZeroFields[0]
}

// If the map is nil or we're purposely zeroing fields, make a new map
if valMap.IsNil() || d.config.ZeroFields {
if valMap.IsNil() || (!ignoreZeroFields && d.config.ZeroFields) {
// Make a new map to hold our result
mapType := reflect.MapOf(valKeyType, valElemType)
valMap = reflect.MakeMap(mapType)
Expand All @@ -803,7 +812,8 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er

case reflect.Array, reflect.Slice:
if d.config.WeaklyTypedInput {
return d.decodeMapFromSlice(name, dataVal, val, valMap)
ret := d.decodeMapFromSlice(name, dataVal, val, valMap)
return ret
}

fallthrough
Expand All @@ -821,9 +831,11 @@ func (d *Decoder) decodeMapFromSlice(name string, dataVal reflect.Value, val ref
}

for i := 0; i < dataVal.Len(); i++ {
// shoule not ignore config.ZeroFields when i == 0
shouldIgnoreZeroFields := i != 0
err := d.decode(
name+"["+strconv.Itoa(i)+"]",
dataVal.Index(i).Interface(), val)
dataVal.Index(i).Interface(), val, shouldIgnoreZeroFields)
if err != nil {
return err
}
Expand Down Expand Up @@ -959,7 +971,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
addrVal := reflect.New(vMap.Type())
reflect.Indirect(addrVal).Set(vMap)

err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal))
err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal), false)
if err != nil {
return err
}
Expand Down
63 changes: 63 additions & 0 deletions mapstructure_test.go
Expand Up @@ -1587,6 +1587,69 @@ func TestSliceToMap(t *testing.T) {
}
}

// better to use function option for config option. like: https://github.com/timestee/optiongen
func weakDecodeZeroFields(input, output interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
ZeroFields: true,
}

decoder, err := NewDecoder(config)
if err != nil {
return err
}

return decoder.Decode(input)
}

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

input := []map[string]interface{}{
{
"foo": "bar",
},
{
"bar": "baz",
},
}
{
var result map[string]interface{}
err := weakDecodeZeroFields(input, &result)
if err != nil {
t.Fatalf("got an error: %s", err)
}

expected := map[string]interface{}{
"foo": "bar",
"bar": "baz",
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("bad: %#v", result)
}
}

{
result := map[string]interface{}{
"should_be_deleted": "should_be_deleted",
}
err := weakDecodeZeroFields(input, &result)
if err != nil {
t.Fatalf("got an error: %s", err)
}

expected := map[string]interface{}{
"foo": "bar",
"bar": "baz",
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("bad: %#v", result)
}
}
}

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

Expand Down