From fe1114d461edf37e8424d4e0eeffa5e2f69a3783 Mon Sep 17 00:00:00 2001 From: Daenney Date: Mon, 17 Mar 2025 17:50:44 +0100 Subject: [PATCH] feat: OmitZero and OmitEmpty This splits OmitEmpty into OmitEmpty and OmitZero. OmitEmpty will omit anything that is zero as well as a slice and map of length 0. OmitZero only omits the zero type, either by checking the return value of the IsZero method or if that's not implemented by reflect.Value.IsZero. This by default retains the empty map and slice, since initialised but 0-length maps and slices are not zero. --- repr.go | 33 ++++++++++++++++++++++++++++++++- repr_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/repr.go b/repr.go index c03eb06..7e3b5ad 100644 --- a/repr.go +++ b/repr.go @@ -63,7 +63,24 @@ func Indent(indent string) Option { return func(o *Printer) { o.indent = indent func NoIndent() Option { return Indent("") } // OmitEmpty sets whether empty field members should be omitted from output. -func OmitEmpty(omitEmpty bool) Option { return func(o *Printer) { o.omitEmpty = omitEmpty } } +// +// Empty field members are either the zero type, or zero-length maps and slices. +func OmitEmpty(omitEmpty bool) Option { + return func(o *Printer) { + o.omitEmpty = omitEmpty + } +} + +// OmitZero sets whether zero field members should be omitted from output. +// +// Field members are considered zero if they have an IsZero method that returns +// true, or if [reflect.Value.IsZero] returns true. Empty maps and slices are +// not zero. +func OmitZero(omitZero bool) Option { + return func(o *Printer) { + o.omitZero = omitZero + } +} // ExplicitTypes adds explicit typing to slice and map struct values that would normally be inferred by Go. func ExplicitTypes(ok bool) Option { return func(o *Printer) { o.explicitTypes = true } } @@ -95,6 +112,7 @@ func AlwaysIncludeType() Option { return func(o *Printer) { o.alwaysIncludeType type Printer struct { indent string omitEmpty bool + omitZero bool ignoreGoStringer bool ignorePrivate bool alwaysIncludeType bool @@ -110,6 +128,7 @@ func New(w io.Writer, options ...Option) *Printer { w: w, indent: " ", omitEmpty: true, + omitZero: true, exclude: map[reflect.Type]bool{}, } for _, option := range options { @@ -118,6 +137,12 @@ func New(w io.Writer, options ...Option) *Printer { return p } +type isZeroer interface { + IsZero() bool +} + +var isZeroerType = reflect.TypeFor[isZeroer]() + func (p *Printer) nextIndent(indent string) string { if p.indent != "" { return indent + p.indent @@ -261,11 +286,17 @@ func (p *Printer) reprValue(seen map[reflect.Value]bool, v reflect.Value, indent if p.ignorePrivate && !f.CanInterface() { continue } + + if p.omitZero && ((ft.Implements(isZeroerType) && f.Interface().(isZeroer).IsZero()) || f.IsZero()) { + continue + } + if p.omitEmpty && (f.IsZero() || ft.Kind() == reflect.Slice && f.Len() == 0 || ft.Kind() == reflect.Map && f.Len() == 0) { continue } + if previous && p.indent == "" { fmt.Fprintf(p.w, ", ") } diff --git a/repr_test.go b/repr_test.go index 477f75f..752a515 100644 --- a/repr_test.go +++ b/repr_test.go @@ -57,6 +57,34 @@ func TestReprEmptySliceMapFields(t *testing.T) { equal(t, `struct { S []string; M map[string]string; NZ []string }{NZ: []string{"a", "b"}}`, String(v, OmitEmpty(true))) } +type zeroTest struct { + i int +} + +var _ isZeroer = (*zeroTest)(nil) + +func (z zeroTest) IsZero() bool { + return z.i == 100 +} + +func TestReprZeroSliceMapFields(t *testing.T) { + v := struct { + ZSl []string + Sl []string + ZM map[string]string + M map[string]string + ZI int + I int + ZS string + S string + ZB bool + B bool + ZC zeroTest + C zeroTest + }{[]string{}, []string{"a", "b"}, map[string]string{}, map[string]string{"a": "b"}, 0, 1, "", "a", false, true, zeroTest{i: 100}, zeroTest{i: 10}} + equal(t, `struct { ZSl []string; Sl []string; ZM map[string]string; M map[string]string; ZI int; I int; ZS string; S string; ZB bool; B bool; ZC repr.zeroTest; C repr.zeroTest }{ZSl: []string{}, Sl: []string{"a", "b"}, ZM: map[string]string{}, M: map[string]string{"a": "b"}, I: 1, S: "a", B: true, C: repr.zeroTest{i: 10}}`, String(v, OmitEmpty(false), OmitZero(true))) +} + func TestReprStringArray(t *testing.T) { equal(t, "[]string{\"a\", \"b\"}", String([]string{"a", "b"})) }