Skip to content

Commit de00bf1

Browse files
committed
encoding/xml: add omitzero option
Fixes #69857 Change-Id: Ib224b85aa96dad89ead944aa9f65e9e34d9bba5e
1 parent 7a38975 commit de00bf1

File tree

3 files changed

+241
-1
lines changed

3 files changed

+241
-1
lines changed

src/encoding/xml/marshal.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
436436
return nil
437437
}
438438

439+
if finfo != nil && finfo.flags&fOmitZero != 0 &&
440+
(finfo.isZero == nil && val.IsZero() || (finfo.isZero != nil && finfo.isZero(val))) {
441+
return nil
442+
}
443+
439444
// Drill into interfaces and pointers.
440445
// This can turn into an infinite loop given a cyclic chain,
441446
// but it matches the Go 1 behavior.
@@ -535,6 +540,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
535540
continue
536541
}
537542

543+
if finfo.flags&fOmitZero != 0 && (!fv.IsValid() ||
544+
(finfo.isZero == nil && fv.IsZero() || (finfo.isZero != nil && finfo.isZero(fv)))) {
545+
continue
546+
}
547+
538548
if fv.Kind() == reflect.Interface && fv.IsNil() {
539549
continue
540550
}

src/encoding/xml/marshal_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,3 +2589,191 @@ func TestClose(t *testing.T) {
25892589
})
25902590
}
25912591
}
2592+
2593+
type OptionalsEmpty struct {
2594+
Sr string `xml:"sr"`
2595+
So string `xml:"so,omitempty"`
2596+
Sw string `xml:"-"`
2597+
2598+
Ir int `xml:"omitempty"` // actually named omitempty, not an option
2599+
Io int `xml:"io,omitempty"`
2600+
2601+
Slr []string `xml:"slr,random"`
2602+
Slo []string `xml:"slo,omitempty"`
2603+
2604+
Fr float64 `xml:"fr"`
2605+
Fo float64 `xml:"fo,omitempty"`
2606+
2607+
Br bool `xml:"br"`
2608+
Bo bool `xml:"bo,omitempty"`
2609+
2610+
Ur uint `xml:"ur"`
2611+
Uo uint `xml:"uo,omitempty"`
2612+
2613+
Str struct{} `xml:"str"`
2614+
Sto struct{} `xml:"sto,omitempty"`
2615+
}
2616+
2617+
func TestOmitEmpty(t *testing.T) {
2618+
const want = `<OptionalsEmpty>
2619+
<sr></sr>
2620+
<omitempty>0</omitempty>
2621+
<fr>0</fr>
2622+
<br>false</br>
2623+
<ur>0</ur>
2624+
<str></str>
2625+
<sto></sto>
2626+
</OptionalsEmpty>`
2627+
var o OptionalsEmpty
2628+
o.Sw = "something"
2629+
2630+
got, err := MarshalIndent(&o, "", " ")
2631+
if err != nil {
2632+
t.Fatalf("MarshalIndent error: %v", err)
2633+
}
2634+
if got := string(got); got != want {
2635+
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
2636+
}
2637+
}
2638+
2639+
type NonZeroStruct struct{}
2640+
2641+
func (nzs NonZeroStruct) IsZero() bool {
2642+
return false
2643+
}
2644+
2645+
type NoPanicStruct struct {
2646+
Int int `xml:"int,omitzero"`
2647+
}
2648+
2649+
func (nps *NoPanicStruct) IsZero() bool {
2650+
return nps.Int != 0
2651+
}
2652+
2653+
type OptionalsZero struct {
2654+
Sr string `xml:"sr"`
2655+
So string `xml:"so,omitzero"`
2656+
Sw string `xml:"-"`
2657+
2658+
Ir int `xml:"omitzero"` // actually named omitzero, not an option
2659+
Io int `xml:"io,omitzero"`
2660+
2661+
Slr []string `xml:"slr,random"`
2662+
Slo []string `xml:"slo,omitzero"`
2663+
SloNonNil []string `xml:"slononnil,omitzero"`
2664+
2665+
Fr float64 `xml:"fr"`
2666+
Fo float64 `xml:"fo,omitzero"`
2667+
Foo float64 `xml:"foo,omitzero"`
2668+
Foo2 [2]float64 `xml:"foo2,omitzero"`
2669+
2670+
Br bool `xml:"br"`
2671+
Bo bool `xml:"bo,omitzero"`
2672+
2673+
Ur uint `xml:"ur"`
2674+
Uo uint `xml:"uo,omitzero"`
2675+
2676+
Str struct{} `xml:"str"`
2677+
Sto struct{} `xml:"sto,omitzero"`
2678+
2679+
Time time.Time `xml:"time,omitzero"`
2680+
TimeLocal time.Time `xml:"timelocal,omitzero"`
2681+
Nzs NonZeroStruct `xml:"nzs,omitzero"`
2682+
2683+
NilIsZeroer isZeroer `xml:"niliszeroer,omitzero"` // nil interface
2684+
NonNilIsZeroer isZeroer `xml:"nonniliszeroer,omitzero"` // non-nil interface
2685+
NoPanicStruct0 isZeroer `xml:"nps0,omitzero"` // non-nil interface with nil pointer
2686+
NoPanicStruct1 isZeroer `xml:"nps1,omitzero"` // non-nil interface with non-nil pointer
2687+
NoPanicStruct2 *NoPanicStruct `xml:"nps2,omitzero"` // nil pointer
2688+
NoPanicStruct3 *NoPanicStruct `xml:"nps3,omitzero"` // non-nil pointer
2689+
NoPanicStruct4 NoPanicStruct `xml:"nps4,omitzero"` // concrete type
2690+
}
2691+
2692+
func TestOmitZero(t *testing.T) {
2693+
const want = `<OptionalsZero>
2694+
<sr></sr>
2695+
<omitzero>0</omitzero>
2696+
<fr>0</fr>
2697+
<br>false</br>
2698+
<ur>0</ur>
2699+
<str></str>
2700+
<nzs></nzs>
2701+
<nps1></nps1>
2702+
<nps3></nps3>
2703+
<nps4></nps4>
2704+
</OptionalsZero>`
2705+
var o OptionalsZero
2706+
o.Sw = "something"
2707+
o.SloNonNil = make([]string, 0)
2708+
2709+
o.Foo = -0
2710+
o.Foo2 = [2]float64{+0, -0}
2711+
2712+
o.TimeLocal = time.Time{}.Local()
2713+
2714+
o.NonNilIsZeroer = time.Time{}
2715+
o.NoPanicStruct0 = (*NoPanicStruct)(nil)
2716+
o.NoPanicStruct1 = &NoPanicStruct{}
2717+
o.NoPanicStruct3 = &NoPanicStruct{}
2718+
2719+
got, err := MarshalIndent(&o, "", " ")
2720+
if err != nil {
2721+
t.Fatalf("MarshalIndent error: %v", err)
2722+
}
2723+
if got := string(got); got != want {
2724+
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
2725+
}
2726+
}
2727+
2728+
type OptionalsEmptyZero struct {
2729+
Sr string `xml:"sr"`
2730+
So string `xml:"so,omitempty,omitzero"`
2731+
Sw string `xml:"-"`
2732+
2733+
Io int `xml:"io,omitempty,omitzero"`
2734+
2735+
Slr []string `xml:"slr,random"`
2736+
Slo []string `xml:"slo,omitempty,omitzero"`
2737+
SloNonNil []string `xml:"slononnil,omitempty,omitzero"`
2738+
2739+
Fr float64 `xml:"fr"`
2740+
Fo float64 `xml:"fo,omitempty,omitzero"`
2741+
2742+
Br bool `xml:"br"`
2743+
Bo bool `xml:"bo,omitempty,omitzero"`
2744+
2745+
Ur uint `xml:"ur"`
2746+
Uo uint `xml:"uo,omitempty,omitzero"`
2747+
2748+
Str struct{} `xml:"str"`
2749+
Sto struct{} `xml:"sto,omitempty,omitzero"`
2750+
2751+
Time time.Time `xml:"time,omitempty,omitzero"`
2752+
Nzs NonZeroStruct `xml:"nzs,omitempty,omitzero"`
2753+
}
2754+
2755+
func TestOmitEmptyZero(t *testing.T) {
2756+
const want = `<OptionalsEmptyZero>
2757+
<sr></sr>
2758+
<fr>0</fr>
2759+
<br>false</br>
2760+
<ur>0</ur>
2761+
<str></str>
2762+
<nzs></nzs>
2763+
</OptionalsEmptyZero>`
2764+
var o OptionalsEmptyZero
2765+
o.Sw = "something"
2766+
o.SloNonNil = make([]string, 0)
2767+
2768+
got, err := MarshalIndent(&o, "", " ")
2769+
if err != nil {
2770+
t.Fatalf("MarshalIndent error: %v", err)
2771+
}
2772+
if got := string(got); got != want {
2773+
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
2774+
}
2775+
}
2776+
2777+
func indentNewlines(s string) string {
2778+
return strings.Join(strings.Split(s, "\n"), "\n\t")
2779+
}

src/encoding/xml/typeinfo.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type fieldInfo struct {
2424
xmlns string
2525
flags fieldFlags
2626
parents []string
27+
isZero func(reflect.Value) bool
2728
}
2829

2930
type fieldFlags int
@@ -38,6 +39,7 @@ const (
3839
fAny
3940

4041
fOmitEmpty
42+
fOmitZero
4143

4244
fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
4345

@@ -109,6 +111,12 @@ func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
109111
return ti.(*typeInfo), nil
110112
}
111113

114+
type isZeroer interface {
115+
IsZero() bool
116+
}
117+
118+
var isZeroerType = reflect.TypeFor[isZeroer]()
119+
112120
// structFieldInfo builds and returns a fieldInfo for f.
113121
func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, error) {
114122
finfo := &fieldInfo{idx: f.Index}
@@ -141,6 +149,39 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
141149
finfo.flags |= fAny
142150
case "omitempty":
143151
finfo.flags |= fOmitEmpty
152+
case "omitzero":
153+
finfo.flags |= fOmitZero
154+
t := f.Type
155+
// Provide a function that uses a type's IsZero method.
156+
switch {
157+
case t.Kind() == reflect.Interface && t.Implements(isZeroerType):
158+
finfo.isZero = func(v reflect.Value) bool {
159+
// Avoid panics calling IsZero on a nil interface or
160+
// non-nil interface with nil pointer.
161+
return v.IsNil() ||
162+
(v.Elem().Kind() == reflect.Pointer && v.Elem().IsNil()) ||
163+
v.Interface().(isZeroer).IsZero()
164+
}
165+
case t.Kind() == reflect.Pointer && t.Implements(isZeroerType):
166+
finfo.isZero = func(v reflect.Value) bool {
167+
// Avoid panics calling IsZero on nil pointer.
168+
return v.IsNil() || v.Interface().(isZeroer).IsZero()
169+
}
170+
case t.Implements(isZeroerType):
171+
finfo.isZero = func(v reflect.Value) bool {
172+
return v.Interface().(isZeroer).IsZero()
173+
}
174+
case reflect.PointerTo(t).Implements(isZeroerType):
175+
finfo.isZero = func(v reflect.Value) bool {
176+
if !v.CanAddr() {
177+
// Temporarily box v so we can take the address.
178+
v2 := reflect.New(v.Type()).Elem()
179+
v2.Set(v)
180+
v = v2
181+
}
182+
return v.Addr().Interface().(isZeroer).IsZero()
183+
}
184+
}
144185
}
145186
}
146187

@@ -160,7 +201,8 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
160201
if finfo.flags&fMode == fAny {
161202
finfo.flags |= fElement
162203
}
163-
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
204+
if (finfo.flags&fOmitEmpty != 0 || finfo.flags&fOmitZero != 0) &&
205+
finfo.flags&(fElement|fAttr) == 0 {
164206
valid = false
165207
}
166208
if !valid {

0 commit comments

Comments
 (0)