diff --git a/src/encoding/xml/marshal.go b/src/encoding/xml/marshal.go
index 133503fa2de41c..0a55c1517c2cfe 100644
--- a/src/encoding/xml/marshal.go
+++ b/src/encoding/xml/marshal.go
@@ -436,6 +436,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
return nil
}
+ if finfo != nil && finfo.flags&fOmitZero != 0 &&
+ (finfo.isZero == nil && val.IsZero() || (finfo.isZero != nil && finfo.isZero(val))) {
+ return nil
+ }
+
// Drill into interfaces and pointers.
// This can turn into an infinite loop given a cyclic chain,
// but it matches the Go 1 behavior.
@@ -535,6 +540,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
continue
}
+ if finfo.flags&fOmitZero != 0 && (!fv.IsValid() ||
+ (finfo.isZero == nil && fv.IsZero() || (finfo.isZero != nil && finfo.isZero(fv)))) {
+ continue
+ }
+
if fv.Kind() == reflect.Interface && fv.IsNil() {
continue
}
diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go
index b8bce7170a60b6..b4b6311df748b1 100644
--- a/src/encoding/xml/marshal_test.go
+++ b/src/encoding/xml/marshal_test.go
@@ -2589,3 +2589,191 @@ func TestClose(t *testing.T) {
})
}
}
+
+type OptionalsEmpty struct {
+ Sr string `xml:"sr"`
+ So string `xml:"so,omitempty"`
+ Sw string `xml:"-"`
+
+ Ir int `xml:"omitempty"` // actually named omitempty, not an option
+ Io int `xml:"io,omitempty"`
+
+ Slr []string `xml:"slr,random"`
+ Slo []string `xml:"slo,omitempty"`
+
+ Fr float64 `xml:"fr"`
+ Fo float64 `xml:"fo,omitempty"`
+
+ Br bool `xml:"br"`
+ Bo bool `xml:"bo,omitempty"`
+
+ Ur uint `xml:"ur"`
+ Uo uint `xml:"uo,omitempty"`
+
+ Str struct{} `xml:"str"`
+ Sto struct{} `xml:"sto,omitempty"`
+}
+
+func TestOmitEmpty(t *testing.T) {
+ const want = `
+
+ 0
+ 0
+
false
+ 0
+
+
+`
+ var o OptionalsEmpty
+ o.Sw = "something"
+
+ got, err := MarshalIndent(&o, "", " ")
+ if err != nil {
+ t.Fatalf("MarshalIndent error: %v", err)
+ }
+ if got := string(got); got != want {
+ t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
+ }
+}
+
+type NonZeroStruct struct{}
+
+func (nzs NonZeroStruct) IsZero() bool {
+ return false
+}
+
+type NoPanicStruct struct {
+ Int int `xml:"int,omitzero"`
+}
+
+func (nps *NoPanicStruct) IsZero() bool {
+ return nps.Int != 0
+}
+
+type OptionalsZero struct {
+ Sr string `xml:"sr"`
+ So string `xml:"so,omitzero"`
+ Sw string `xml:"-"`
+
+ Ir int `xml:"omitzero"` // actually named omitzero, not an option
+ Io int `xml:"io,omitzero"`
+
+ Slr []string `xml:"slr,random"`
+ Slo []string `xml:"slo,omitzero"`
+ SloNonNil []string `xml:"slononnil,omitzero"`
+
+ Fr float64 `xml:"fr"`
+ Fo float64 `xml:"fo,omitzero"`
+ Foo float64 `xml:"foo,omitzero"`
+ Foo2 [2]float64 `xml:"foo2,omitzero"`
+
+ Br bool `xml:"br"`
+ Bo bool `xml:"bo,omitzero"`
+
+ Ur uint `xml:"ur"`
+ Uo uint `xml:"uo,omitzero"`
+
+ Str struct{} `xml:"str"`
+ Sto struct{} `xml:"sto,omitzero"`
+
+ Time time.Time `xml:"time,omitzero"`
+ TimeLocal time.Time `xml:"timelocal,omitzero"`
+ Nzs NonZeroStruct `xml:"nzs,omitzero"`
+
+ NilIsZeroer isZeroer `xml:"niliszeroer,omitzero"` // nil interface
+ NonNilIsZeroer isZeroer `xml:"nonniliszeroer,omitzero"` // non-nil interface
+ NoPanicStruct0 isZeroer `xml:"nps0,omitzero"` // non-nil interface with nil pointer
+ NoPanicStruct1 isZeroer `xml:"nps1,omitzero"` // non-nil interface with non-nil pointer
+ NoPanicStruct2 *NoPanicStruct `xml:"nps2,omitzero"` // nil pointer
+ NoPanicStruct3 *NoPanicStruct `xml:"nps3,omitzero"` // non-nil pointer
+ NoPanicStruct4 NoPanicStruct `xml:"nps4,omitzero"` // concrete type
+}
+
+func TestOmitZero(t *testing.T) {
+ const want = `
+
+ 0
+ 0
+
false
+ 0
+
+
+
+
+
+`
+ var o OptionalsZero
+ o.Sw = "something"
+ o.SloNonNil = make([]string, 0)
+
+ o.Foo = -0
+ o.Foo2 = [2]float64{+0, -0}
+
+ o.TimeLocal = time.Time{}.Local()
+
+ o.NonNilIsZeroer = time.Time{}
+ o.NoPanicStruct0 = (*NoPanicStruct)(nil)
+ o.NoPanicStruct1 = &NoPanicStruct{}
+ o.NoPanicStruct3 = &NoPanicStruct{}
+
+ got, err := MarshalIndent(&o, "", " ")
+ if err != nil {
+ t.Fatalf("MarshalIndent error: %v", err)
+ }
+ if got := string(got); got != want {
+ t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
+ }
+}
+
+type OptionalsEmptyZero struct {
+ Sr string `xml:"sr"`
+ So string `xml:"so,omitempty,omitzero"`
+ Sw string `xml:"-"`
+
+ Io int `xml:"io,omitempty,omitzero"`
+
+ Slr []string `xml:"slr,random"`
+ Slo []string `xml:"slo,omitempty,omitzero"`
+ SloNonNil []string `xml:"slononnil,omitempty,omitzero"`
+
+ Fr float64 `xml:"fr"`
+ Fo float64 `xml:"fo,omitempty,omitzero"`
+
+ Br bool `xml:"br"`
+ Bo bool `xml:"bo,omitempty,omitzero"`
+
+ Ur uint `xml:"ur"`
+ Uo uint `xml:"uo,omitempty,omitzero"`
+
+ Str struct{} `xml:"str"`
+ Sto struct{} `xml:"sto,omitempty,omitzero"`
+
+ Time time.Time `xml:"time,omitempty,omitzero"`
+ Nzs NonZeroStruct `xml:"nzs,omitempty,omitzero"`
+}
+
+func TestOmitEmptyZero(t *testing.T) {
+ const want = `
+
+ 0
+
false
+ 0
+
+
+`
+ var o OptionalsEmptyZero
+ o.Sw = "something"
+ o.SloNonNil = make([]string, 0)
+
+ got, err := MarshalIndent(&o, "", " ")
+ if err != nil {
+ t.Fatalf("MarshalIndent error: %v", err)
+ }
+ if got := string(got); got != want {
+ t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
+ }
+}
+
+func indentNewlines(s string) string {
+ return strings.Join(strings.Split(s, "\n"), "\n\t")
+}
diff --git a/src/encoding/xml/typeinfo.go b/src/encoding/xml/typeinfo.go
index b18ed284a690ca..a5061c872226d7 100644
--- a/src/encoding/xml/typeinfo.go
+++ b/src/encoding/xml/typeinfo.go
@@ -24,6 +24,7 @@ type fieldInfo struct {
xmlns string
flags fieldFlags
parents []string
+ isZero func(reflect.Value) bool
}
type fieldFlags int
@@ -38,6 +39,7 @@ const (
fAny
fOmitEmpty
+ fOmitZero
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
@@ -109,6 +111,12 @@ func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
return ti.(*typeInfo), nil
}
+type isZeroer interface {
+ IsZero() bool
+}
+
+var isZeroerType = reflect.TypeFor[isZeroer]()
+
// structFieldInfo builds and returns a fieldInfo for f.
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
finfo := &fieldInfo{idx: f.Index}
@@ -141,6 +149,39 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
finfo.flags |= fAny
case "omitempty":
finfo.flags |= fOmitEmpty
+ case "omitzero":
+ finfo.flags |= fOmitZero
+ t := f.Type
+ // Provide a function that uses a type's IsZero method.
+ switch {
+ case t.Kind() == reflect.Interface && t.Implements(isZeroerType):
+ finfo.isZero = func(v reflect.Value) bool {
+ // Avoid panics calling IsZero on a nil interface or
+ // non-nil interface with nil pointer.
+ return v.IsNil() ||
+ (v.Elem().Kind() == reflect.Pointer && v.Elem().IsNil()) ||
+ v.Interface().(isZeroer).IsZero()
+ }
+ case t.Kind() == reflect.Pointer && t.Implements(isZeroerType):
+ finfo.isZero = func(v reflect.Value) bool {
+ // Avoid panics calling IsZero on nil pointer.
+ return v.IsNil() || v.Interface().(isZeroer).IsZero()
+ }
+ case t.Implements(isZeroerType):
+ finfo.isZero = func(v reflect.Value) bool {
+ return v.Interface().(isZeroer).IsZero()
+ }
+ case reflect.PointerTo(t).Implements(isZeroerType):
+ finfo.isZero = func(v reflect.Value) bool {
+ if !v.CanAddr() {
+ // Temporarily box v so we can take the address.
+ v2 := reflect.New(v.Type()).Elem()
+ v2.Set(v)
+ v = v2
+ }
+ return v.Addr().Interface().(isZeroer).IsZero()
+ }
+ }
}
}
@@ -160,7 +201,8 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
if finfo.flags&fMode == fAny {
finfo.flags |= fElement
}
- if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
+ if (finfo.flags&fOmitEmpty != 0 || finfo.flags&fOmitZero != 0) &&
+ finfo.flags&(fElement|fAttr) == 0 {
valid = false
}
if !valid {