Skip to content

Commit

Permalink
omitempty also considers structs with all zero values "empty".
Browse files Browse the repository at this point in the history
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
  • Loading branch information
arp242 committed Jun 7, 2022
1 parent a8d6de8 commit 88c77e9
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 0 deletions.
2 changes: 2 additions & 0 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
64 changes: 64 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,75 @@ 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"`
String string `toml:"string,omitempty"`
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
Expand All @@ -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"
Expand Down

0 comments on commit 88c77e9

Please sign in to comment.