Skip to content

Commit 1a66224

Browse files
Merge pull request #98 from Crystalix007/main
Add omitzero tag.
2 parents 75046f5 + 21da843 commit 1a66224

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

mapstructure.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,36 @@
115115
//
116116
// When decoding from a struct to any other value, you may use the
117117
// ",omitempty" suffix on your tag to omit that value if it equates to
118-
// the zero value. The zero value of all types is specified in the Go
119-
// specification.
118+
// the zero value, or a zero-length element. The zero value of all types is
119+
// specified in the Go specification.
120120
//
121121
// For example, the zero type of a numeric type is zero ("0"). If the struct
122122
// field value is zero and a numeric type, the field is empty, and it won't
123-
// be encoded into the destination type.
123+
// be encoded into the destination type. And likewise for the URLs field, if the
124+
// slice is nil or empty, it won't be encoded into the destination type.
124125
//
125126
// type Source struct {
126-
// Age int `mapstructure:",omitempty"`
127+
// Age int `mapstructure:",omitempty"`
128+
// URLs []string `mapstructure:",omitempty"`
129+
// }
130+
//
131+
// # Omit Zero Values
132+
//
133+
// When decoding from a struct to any other value, you may use the
134+
// ",omitzero" suffix on your tag to omit that value if it equates to the zero
135+
// value. The zero value of all types is specified in the Go specification.
136+
//
137+
// For example, the zero type of a numeric type is zero ("0"). If the struct
138+
// field value is zero and a numeric type, the field is empty, and it won't
139+
// be encoded into the destination type. And likewise for the URLs field, if the
140+
// slice is nil, it won't be encoded into the destination type.
141+
//
142+
// Note that if the field is a slice, and it is empty but not nil, it will
143+
// still be encoded into the destination type.
144+
//
145+
// type Source struct {
146+
// Age int `mapstructure:",omitzero"`
147+
// URLs []string `mapstructure:",omitzero"`
127148
// }
128149
//
129150
// # Unexported fields
@@ -1011,6 +1032,11 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
10111032
continue
10121033
}
10131034

1035+
// If "omitzero" is specified in the tag, it ignores zero values.
1036+
if strings.Index(tagValue[index+1:], "omitzero") != -1 && v.IsZero() {
1037+
continue
1038+
}
1039+
10141040
// If "squash" is specified in the tag, we squash the field down.
10151041
squash = squash || strings.Contains(tagValue[index+1:], d.config.SquashTagOption)
10161042
if squash {

mapstructure_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,21 @@ type StructWithOmitEmpty struct {
255255
OmitNestedField *Nested `mapstructure:"omittable-nested,omitempty"`
256256
}
257257

258+
type StructWithOmitZero struct {
259+
VisibleStringField string `mapstructure:"visible-string"`
260+
OmitStringField string `mapstructure:"omittable-string,omitzero"`
261+
VisibleIntField int `mapstructure:"visible-int"`
262+
OmitIntField int `mapstructure:"omittable-int,omitzero"`
263+
VisibleFloatField float64 `mapstructure:"visible-float"`
264+
OmitFloatField float64 `mapstructure:"omittable-float,omitzero"`
265+
VisibleSliceField []interface{} `mapstructure:"visible-slice"`
266+
OmitSliceField []interface{} `mapstructure:"omittable-slice,omitzero"`
267+
VisibleMapField map[string]interface{} `mapstructure:"visible-map"`
268+
OmitMapField map[string]interface{} `mapstructure:"omittable-map,omitzero"`
269+
NestedField *Nested `mapstructure:"visible-nested"`
270+
OmitNestedField *Nested `mapstructure:"omittable-nested,omitzero"`
271+
}
272+
258273
type TypeConversionResult struct {
259274
IntToFloat float32
260275
IntToUint uint
@@ -2932,6 +2947,88 @@ func TestDecode_StructTaggedWithOmitempty_KeepNonEmptyValues(t *testing.T) {
29322947
}
29332948
}
29342949

2950+
func TestDecode_StructTaggedWithOmitzero_KeepNonZeroValues(t *testing.T) {
2951+
t.Parallel()
2952+
2953+
input := &StructWithOmitZero{
2954+
VisibleStringField: "",
2955+
OmitStringField: "string",
2956+
VisibleIntField: 0,
2957+
OmitIntField: 1,
2958+
VisibleFloatField: 0.0,
2959+
OmitFloatField: 1.0,
2960+
VisibleSliceField: nil,
2961+
OmitSliceField: []interface{}{},
2962+
VisibleMapField: nil,
2963+
OmitMapField: map[string]interface{}{},
2964+
NestedField: nil,
2965+
OmitNestedField: &Nested{},
2966+
}
2967+
2968+
var emptySlice []interface{}
2969+
var emptyMap map[string]interface{}
2970+
var emptyNested *Nested
2971+
expected := &map[string]interface{}{
2972+
"visible-string": "",
2973+
"omittable-string": "string",
2974+
"visible-int": 0,
2975+
"omittable-int": 1,
2976+
"visible-float": 0.0,
2977+
"omittable-float": 1.0,
2978+
"visible-slice": emptySlice,
2979+
"omittable-slice": []interface{}{},
2980+
"visible-map": emptyMap,
2981+
"omittable-map": map[string]interface{}{},
2982+
"visible-nested": emptyNested,
2983+
"omittable-nested": &Nested{},
2984+
}
2985+
2986+
actual := &map[string]interface{}{}
2987+
Decode(input, actual)
2988+
2989+
if !reflect.DeepEqual(actual, expected) {
2990+
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
2991+
}
2992+
}
2993+
2994+
func TestDecode_StructTaggedWithOmitzero_DropZeroValues(t *testing.T) {
2995+
t.Parallel()
2996+
2997+
input := &StructWithOmitZero{
2998+
VisibleStringField: "",
2999+
OmitStringField: "",
3000+
VisibleIntField: 0,
3001+
OmitIntField: 0,
3002+
VisibleFloatField: 0.0,
3003+
OmitFloatField: 0.0,
3004+
VisibleSliceField: nil,
3005+
OmitSliceField: nil,
3006+
VisibleMapField: nil,
3007+
OmitMapField: nil,
3008+
NestedField: nil,
3009+
OmitNestedField: nil,
3010+
}
3011+
3012+
var emptySlice []interface{}
3013+
var emptyMap map[string]interface{}
3014+
var emptyNested *Nested
3015+
expected := &map[string]interface{}{
3016+
"visible-string": "",
3017+
"visible-int": 0,
3018+
"visible-float": 0.0,
3019+
"visible-slice": emptySlice,
3020+
"visible-map": emptyMap,
3021+
"visible-nested": emptyNested,
3022+
}
3023+
3024+
actual := &map[string]interface{}{}
3025+
Decode(input, actual)
3026+
3027+
if !reflect.DeepEqual(actual, expected) {
3028+
t.Fatalf("Decode() expected: %#v, got: %#v", expected, actual)
3029+
}
3030+
}
3031+
29353032
func TestDecode_mapToStruct(t *testing.T) {
29363033
type Target struct {
29373034
String string

0 commit comments

Comments
 (0)