From de00bf198c9b519fbf2e70a1cbbb68c6f72b371a Mon Sep 17 00:00:00 2001 From: Jes Cok Date: Mon, 14 Oct 2024 00:09:52 +0800 Subject: [PATCH] encoding/xml: add omitzero option Fixes #69857 Change-Id: Ib224b85aa96dad89ead944aa9f65e9e34d9bba5e --- src/encoding/xml/marshal.go | 10 ++ src/encoding/xml/marshal_test.go | 188 +++++++++++++++++++++++++++++++ src/encoding/xml/typeinfo.go | 44 +++++++- 3 files changed, 241 insertions(+), 1 deletion(-) 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 {