Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Apr 28, 2020
2 parents 9080aac + 331aabc commit 57698e0
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.2.3

* Fix duplicate entries in Keys list with pointer values. [GH-185]

## 1.2.2

* Do not add unsettable (unexported) values to the unused metadata key
Expand Down
61 changes: 53 additions & 8 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@
// "address": "123 Maple St.",
// }
//
// Omit Empty Values
//
// When decoding from a struct to any other value, you may use the
// ",omitempty" suffix on your tag to omit that value if it equates to
// the zero value. The zero value of all types is specified in the Go
// specification.
//
// For example, the zero type of a numeric type is zero ("0"). If the struct
// field value is zero and a numeric type, the field is empty, and it won't
// be encoded into the destination type.
//
// type Source {
// Age int `mapstructure:",omitempty"`
// }
//
// Unexported fields
//
// Since unexported (private) struct fields cannot be set outside the package
Expand All @@ -124,7 +139,6 @@
// type Exported struct {
// private: "" // field is left with an empty string (zero value)
// Public: "I made it through!"
// }
//
// Other Configuration
//
Expand Down Expand Up @@ -404,6 +418,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e

var err error
outputKind := getKind(outVal)
addMetaKey := true
switch outputKind {
case reflect.Bool:
err = d.decodeBool(name, input, outVal)
Expand All @@ -422,7 +437,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
case reflect.Map:
err = d.decodeMap(name, input, outVal)
case reflect.Ptr:
err = d.decodePtr(name, input, outVal)
addMetaKey, err = d.decodePtr(name, input, outVal)
case reflect.Slice:
err = d.decodeSlice(name, input, outVal)
case reflect.Array:
Expand All @@ -436,7 +451,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e

// If we reached here, then we successfully decoded SOMETHING, so
// mark the key as used if we're tracking metainput.
if d.config.Metadata != nil && name != "" {
if addMetaKey && d.config.Metadata != nil && name != "" {
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
}

Expand Down Expand Up @@ -826,6 +841,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 @@ -887,7 +914,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
return nil
}

func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
// If the input data is nil, then we want to just set the output
// pointer to be nil as well.
isNil := data == nil
Expand All @@ -908,7 +935,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
val.Set(nilValue)
}

return nil
return true, nil
}

// Create an element of the concrete (non pointer) type and decode
Expand All @@ -922,16 +949,16 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
}

if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
return err
return false, err
}

val.Set(realVal)
} else {
if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
return err
return false, err
}
}
return nil
return false, nil
}

func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
Expand Down Expand Up @@ -1319,6 +1346,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
Original file line number Diff line number Diff line change
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]
}
117 changes: 116 additions & 1 deletion mapstructure_test.go
Original file line number Diff line number Diff line change
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 @@ -1939,6 +1980,11 @@ func TestDecodeMetadata(t *testing.T) {
func TestMetadata(t *testing.T) {
t.Parallel()

type testResult struct {
Vfoo string
Vbar BasicPointer
}

input := map[string]interface{}{
"vfoo": "foo",
"vbar": map[string]interface{}{
Expand All @@ -1951,7 +1997,7 @@ func TestMetadata(t *testing.T) {
}

var md Metadata
var result Nested
var result testResult
config := &DecoderConfig{
Metadata: &md,
Result: &result,
Expand Down Expand Up @@ -2118,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 57698e0

Please sign in to comment.