Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/perenecabuto/mapstructure
Browse files Browse the repository at this point in the history
…into perenecabuto-master
  • Loading branch information
mitchellh committed Apr 28, 2020
2 parents 5829d22 + bf4a414 commit ca9bdad
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
30 changes: 30 additions & 0 deletions mapstructure.go
Expand Up @@ -801,6 +801,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
tagValue := f.Tag.Get(d.config.TagName)
tagParts := strings.Split(tagValue, ",")

// If "omitempty" is specified in the tag, it ignores empty values.
omitempty := false
for _, tag := range tagParts[1:] {
if tag == "omitempty" {
omitempty = true
break
}
}
if omitempty && isEmptyValue(v) {
continue
}

// Determine the name of the key in the map
keyName := f.Name
if tagParts[0] != "" {
Expand Down Expand Up @@ -1294,6 +1306,24 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
return nil
}

func isEmptyValue(v reflect.Value) bool {
switch getKind(v) {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}

func getKind(val reflect.Value) reflect.Kind {
kind := val.Kind()

Expand Down
27 changes: 27 additions & 0 deletions mapstructure_examples_test.go
Expand Up @@ -227,3 +227,30 @@ func ExampleDecode_remainingData() {
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}}
}

func ExampleDecode_omitempty() {
// Add omitempty annotation to avoid map keys for empty values
type Family struct {
LastName string
}
type Location struct {
City string
}
type Person struct {
*Family `mapstructure:",omitempty"`
*Location `mapstructure:",omitempty"`
Age int
FirstName string
}

result := &map[string]interface{}{}
input := Person{FirstName: "Somebody"}
err := Decode(input, &result)
if err != nil {
panic(err)
}

fmt.Printf("%+v", result)
// Output:
// &map[Age:0 FirstName:Somebody]
}
110 changes: 110 additions & 0 deletions mapstructure_test.go
Expand Up @@ -147,6 +147,21 @@ type Remainder struct {
Extra map[string]interface{} `mapstructure:",remain"`
}

type StructWithOmitEmpty struct {
VisibleStringField string `mapstructure:"visible-string"`
OmitStringField string `mapstructure:"omittable-string,omitempty"`
VisibleIntField int `mapstructure:"visible-int"`
OmitIntField int `mapstructure:"omittable-int,omitempty"`
VisibleFloatField float64 `mapstructure:"visible-float"`
OmitFloatField float64 `mapstructure:"omittable-float,omitempty"`
VisibleSliceField []interface{} `mapstructure:"visible-slice"`
OmitSliceField []interface{} `mapstructure:"omittable-slice,omitempty"`
VisibleMapField map[string]interface{} `mapstructure:"visible-map"`
OmitMapField map[string]interface{} `mapstructure:"omittable-map,omitempty"`
NestedField *Nested `mapstructure:"visible-nested"`
OmitNestedField *Nested `mapstructure:"omittable-nested,omitempty"`
}

type TypeConversionResult struct {
IntToFloat float32
IntToUint uint
Expand Down Expand Up @@ -1827,6 +1842,32 @@ func TestDecodeTable(t *testing.T) {
},
false,
},
{
"struct with omitempty tag return non-empty values",
&struct {
VisibleField interface{} `mapstructure:"visible"`
OmitField interface{} `mapstructure:"omittable,omitempty"`
}{
VisibleField: nil,
OmitField: "string",
},
&map[string]interface{}{},
&map[string]interface{}{"visible": nil, "omittable": "string"},
false,
},
{
"struct with omitempty tag ignore empty values",
&struct {
VisibleField interface{} `mapstructure:"visible"`
OmitField interface{} `mapstructure:"omittable,omitempty"`
}{
VisibleField: nil,
OmitField: nil,
},
&map[string]interface{}{},
&map[string]interface{}{"visible": nil},
false,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -2123,6 +2164,75 @@ func TestWeakDecodeMetadata(t *testing.T) {
}
}

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

input := &StructWithOmitEmpty{}

var emptySlice []interface{}
var emptyMap map[string]interface{}
var emptyNested *Nested
expected := &map[string]interface{}{
"visible-string": "",
"visible-int": 0,
"visible-float": 0.0,
"visible-slice": emptySlice,
"visible-map": emptyMap,
"visible-nested": emptyNested,
}

actual := &map[string]interface{}{}
Decode(input, actual)

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
}
}

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

input := &StructWithOmitEmpty{
VisibleStringField: "",
OmitStringField: "string",
VisibleIntField: 0,
OmitIntField: 1,
VisibleFloatField: 0.0,
OmitFloatField: 1.0,
VisibleSliceField: nil,
OmitSliceField: []interface{}{1},
VisibleMapField: nil,
OmitMapField: map[string]interface{}{"k": "v"},
NestedField: nil,
OmitNestedField: &Nested{},
}

var emptySlice []interface{}
var emptyMap map[string]interface{}
var emptyNested *Nested
expected := &map[string]interface{}{
"visible-string": "",
"omittable-string": "string",
"visible-int": 0,
"omittable-int": 1,
"visible-float": 0.0,
"omittable-float": 1.0,
"visible-slice": emptySlice,
"omittable-slice": []interface{}{1},
"visible-map": emptyMap,
"omittable-map": map[string]interface{}{"k": "v"},
"visible-nested": emptyNested,
"omittable-nested": &Nested{},
}

actual := &map[string]interface{}{}
Decode(input, actual)

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
}
}

func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
var result Slice
err := Decode(input, &result)
Expand Down

0 comments on commit ca9bdad

Please sign in to comment.