From 88c77e997043a3690e2436f20d78a422bc33af9b Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Mon, 30 May 2022 00:55:12 +0200 Subject: [PATCH] omitempty also considers structs with all zero values "empty". MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A common use case for this is time.Time, which will get marshaled otherwise. A struct with private fields set is *not* considered to be "empty"; not sure yet if this is the best behaviour 🤔 It is the easiest to implement though. Fixes #353 --- encode.go | 2 ++ encode_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/encode.go b/encode.go index 2cc80e37..5229cfdf 100644 --- a/encode.go +++ b/encode.go @@ -655,6 +655,8 @@ func isEmpty(rv reflect.Value) bool { switch rv.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return rv.Len() == 0 + case reflect.Struct: + return reflect.Zero(rv.Type()).Interface() == rv.Interface() case reflect.Bool: return !rv.Bool() } diff --git a/encode_test.go b/encode_test.go index 0cc73262..974ebc87 100644 --- a/encode_test.go +++ b/encode_test.go @@ -116,6 +116,67 @@ func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) { encodeExpected(t, "array hash with normal hash order", val, expected, nil) } +func TestEncodeOmitEmptyStruct(t *testing.T) { + type ( + T struct{ Int int } + Tpriv struct { + Int int + private int + } + Ttime struct { + Time time.Time + } + ) + + tests := []struct { + in interface{} + want string + }{ + {struct { + F T `toml:"f,omitempty"` + }{}, ""}, + {struct { + F T `toml:"f,omitempty"` + }{T{1}}, "[f]\n Int = 1"}, + + {struct { + F Tpriv `toml:"f,omitempty"` + }{}, ""}, + {struct { + F Tpriv `toml:"f,omitempty"` + }{Tpriv{1, 0}}, "[f]\n Int = 1"}, + + // Private field being set also counts as "not empty". + {struct { + F Tpriv `toml:"f,omitempty"` + }{Tpriv{0, 1}}, "[f]\n Int = 0"}, + + // time.Time is common use case, so test that explicitly. + {struct { + F Ttime `toml:"t,omitempty"` + }{}, ""}, + {struct { + F Ttime `toml:"t,omitempty"` + }{Ttime{time.Time{}.Add(1)}}, "[t]\n Time = 0001-01-01T00:00:00.000000001Z"}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + buf := new(bytes.Buffer) + + err := NewEncoder(buf).Encode(tt.in) + if err != nil { + t.Fatal(err) + } + + have := strings.TrimSpace(buf.String()) + if have != tt.want { + t.Errorf("\nhave:\n%s\nwant:\n%s", have, tt.want) + } + }) + } +} + func TestEncodeWithOmitEmpty(t *testing.T) { type simple struct { Bool bool `toml:"bool,omitempty"` @@ -123,6 +184,7 @@ func TestEncodeWithOmitEmpty(t *testing.T) { Array [0]byte `toml:"array,omitempty"` Slice []int `toml:"slice,omitempty"` Map map[string]string `toml:"map,omitempty"` + Time time.Time `toml:"time,omitempty"` } var v simple @@ -132,10 +194,12 @@ func TestEncodeWithOmitEmpty(t *testing.T) { String: " ", Slice: []int{2, 3, 4}, Map: map[string]string{"foo": "bar"}, + Time: time.Date(1985, 6, 18, 15, 16, 17, 0, time.UTC), } expected := `bool = true string = " " slice = [2, 3, 4] +time = 1985-06-18T15:16:17Z [map] foo = "bar"