From db181f8fd660be86857489f6de94babbf06032ce Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Fri, 22 Aug 2025 11:30:47 +1000 Subject: [PATCH] Add `omitzero` tag support --- marshaler.go | 20 ++++++++++++++ marshaler_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/marshaler.go b/marshaler.go index 334c828c..13193ed5 100644 --- a/marshaler.go +++ b/marshaler.go @@ -161,6 +161,8 @@ func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder { // // The "omitempty" option prevents empty values or groups from being emitted. // +// The "omitzero" option prevents zero values or groups from being emitted. +// // The "commented" option prefixes the value and all its children with a comment // symbol. // @@ -196,6 +198,7 @@ func (enc *Encoder) Encode(v interface{}) error { type valueOptions struct { multiline bool omitempty bool + omitzero bool commented bool comment string } @@ -384,6 +387,10 @@ func shouldOmitEmpty(options valueOptions, v reflect.Value) bool { return options.omitempty && isEmptyValue(v) } +func shouldOmitZero(options valueOptions, v reflect.Value) bool { + return options.omitzero && v.IsZero() +} + func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { var err error @@ -774,6 +781,7 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { options := valueOptions{ multiline: opts.multiline, omitempty: opts.omitempty, + omitzero: opts.omitzero, commented: opts.commented, comment: fieldType.Tag.Get("comment"), } @@ -834,6 +842,7 @@ type tagOptions struct { multiline bool inline bool omitempty bool + omitzero bool commented bool } @@ -862,6 +871,8 @@ func parseTag(tag string) (string, tagOptions) { opts.inline = true case "omitempty": opts.omitempty = true + case "omitzero": + opts.omitzero = true case "commented": opts.commented = true } @@ -896,6 +907,9 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro if shouldOmitEmpty(kv.Options, kv.Value) { continue } + if shouldOmitZero(kv.Options, kv.Value) { + continue + } hasNonEmptyKV = true ctx.setKey(kv.Key) @@ -915,6 +929,9 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro if shouldOmitEmpty(table.Options, table.Value) { continue } + if shouldOmitZero(table.Options, table.Value) { + continue + } if first { first = false if hasNonEmptyKV { @@ -949,6 +966,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte if shouldOmitEmpty(kv.Options, kv.Value) { continue } + if shouldOmitZero(kv.Options, kv.Value) { + continue + } if first { first = false diff --git a/marshaler_test.go b/marshaler_test.go index 675e6d9d..267c70e2 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "math/big" + "net/netip" "reflect" "strings" "testing" @@ -1117,6 +1118,9 @@ func TestEncoderOmitempty(t *testing.T) { Ptr *string `toml:",omitempty,multiline"` Iface interface{} `toml:",omitempty,multiline"` Struct struct{} `toml:",omitempty,multiline"` + Inline struct { + String string `toml:",omitempty,multiline"` + } `toml:",inline"` } d := doc{} @@ -1124,7 +1128,68 @@ func TestEncoderOmitempty(t *testing.T) { b, err := toml.Marshal(d) assert.NoError(t, err) - expected := `` + expected := `Inline = {} +` + + assert.Equal(t, expected, string(b)) +} + +func TestEncoderOmitzero(t *testing.T) { + type doc struct { + String string `toml:",omitzero,multiline"` + Bool bool `toml:",omitzero,multiline"` + Int int `toml:",omitzero,multiline"` + Int8 int8 `toml:",omitzero,multiline"` + Int16 int16 `toml:",omitzero,multiline"` + Int32 int32 `toml:",omitzero,multiline"` + Int64 int64 `toml:",omitzero,multiline"` + Uint uint `toml:",omitzero,multiline"` + Uint8 uint8 `toml:",omitzero,multiline"` + Uint16 uint16 `toml:",omitzero,multiline"` + Uint32 uint32 `toml:",omitzero,multiline"` + Uint64 uint64 `toml:",omitzero,multiline"` + Float32 float32 `toml:",omitzero,multiline"` + Float64 float64 `toml:",omitzero,multiline"` + MapNil map[string]string `toml:",omitzero,multiline"` + Slice []string `toml:",omitzero,multiline"` + Ptr *string `toml:",omitzero,multiline"` + Iface interface{} `toml:",omitzero,multiline"` + Struct struct{} `toml:",omitzero,multiline"` + Time time.Time `toml:",omitzero,multiline"` + IP netip.Addr `toml:",omitzero,multiline"` + Inline struct { + String string `toml:",omitzero,multiline"` + } `toml:",inline"` + } + + d := doc{} + + b, err := toml.Marshal(d) + assert.NoError(t, err) + + expected := `Inline = {} +` + + assert.Equal(t, expected, string(b)) +} + +func TestEncoderOmitzeroOpaqueStruct(t *testing.T) { + type doc struct { + Time time.Time `toml:",omitzero"` + IP netip.Addr `toml:",omitzero"` + } + + d := doc{ + Time: time.Date(2001, 2, 3, 4, 5, 6, 7, time.UTC), + IP: netip.MustParseAddr("192.168.178.35"), + } + + b, err := toml.Marshal(d) + assert.NoError(t, err) + + expected := `Time = 2001-02-03T04:05:06.000000007Z +IP = '192.168.178.35' +` assert.Equal(t, expected, string(b)) }