Skip to content

Commit 28ff18d

Browse files
authored
Merge pull request #356 from BurntSushi/empty
omitempty also considers structs with all zero values "empty".
2 parents a8d6de8 + bd45a18 commit 28ff18d

File tree

6 files changed

+89
-38
lines changed

6 files changed

+89
-38
lines changed

encode.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ type Marshaler interface {
8989
//
9090
// Go maps will be sorted alphabetically by key for deterministic output.
9191
//
92+
// The toml struct tag can be used to provide the key name; if omitted the
93+
// struct field name will be used. If the "omitempty" option is present the
94+
// following value will be skipped:
95+
//
96+
// - arrays, slices, maps, and string with len of 0
97+
// - struct with all zero values
98+
// - bool false
99+
//
100+
// If omitzero is given all int and float types with a value of 0 will be
101+
// skipped.
102+
//
92103
// Encoding Go values without a corresponding TOML representation will return an
93104
// error. Examples of this includes maps with non-string keys, slices with nil
94105
// elements, embedded non-struct types, and nested slices containing maps or
@@ -655,6 +666,8 @@ func isEmpty(rv reflect.Value) bool {
655666
switch rv.Kind() {
656667
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
657668
return rv.Len() == 0
669+
case reflect.Struct:
670+
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
658671
case reflect.Bool:
659672
return !rv.Bool()
660673
}

encode_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,78 @@ func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
116116
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
117117
}
118118

119+
func TestEncodeOmitEmptyStruct(t *testing.T) {
120+
type (
121+
T struct{ Int int }
122+
Tpriv struct {
123+
Int int
124+
private int
125+
}
126+
Ttime struct {
127+
Time time.Time
128+
}
129+
)
130+
131+
tests := []struct {
132+
in interface{}
133+
want string
134+
}{
135+
{struct {
136+
F T `toml:"f,omitempty"`
137+
}{}, ""},
138+
{struct {
139+
F T `toml:"f,omitempty"`
140+
}{T{1}}, "[f]\n Int = 1"},
141+
142+
{struct {
143+
F Tpriv `toml:"f,omitempty"`
144+
}{}, ""},
145+
{struct {
146+
F Tpriv `toml:"f,omitempty"`
147+
}{Tpriv{1, 0}}, "[f]\n Int = 1"},
148+
149+
// Private field being set also counts as "not empty".
150+
{struct {
151+
F Tpriv `toml:"f,omitempty"`
152+
}{Tpriv{0, 1}}, "[f]\n Int = 0"},
153+
154+
// time.Time is common use case, so test that explicitly.
155+
{struct {
156+
F Ttime `toml:"t,omitempty"`
157+
}{}, ""},
158+
{struct {
159+
F Ttime `toml:"t,omitempty"`
160+
}{Ttime{time.Time{}.Add(1)}}, "[t]\n Time = 0001-01-01T00:00:00.000000001Z"},
161+
162+
// TODO: also test with MarshalText, MarshalTOML returning non-zero
163+
// value.
164+
}
165+
166+
for _, tt := range tests {
167+
t.Run("", func(t *testing.T) {
168+
buf := new(bytes.Buffer)
169+
170+
err := NewEncoder(buf).Encode(tt.in)
171+
if err != nil {
172+
t.Fatal(err)
173+
}
174+
175+
have := strings.TrimSpace(buf.String())
176+
if have != tt.want {
177+
t.Errorf("\nhave:\n%s\nwant:\n%s", have, tt.want)
178+
}
179+
})
180+
}
181+
}
182+
119183
func TestEncodeWithOmitEmpty(t *testing.T) {
120184
type simple struct {
121185
Bool bool `toml:"bool,omitempty"`
122186
String string `toml:"string,omitempty"`
123187
Array [0]byte `toml:"array,omitempty"`
124188
Slice []int `toml:"slice,omitempty"`
125189
Map map[string]string `toml:"map,omitempty"`
190+
Time time.Time `toml:"time,omitempty"`
126191
}
127192

128193
var v simple
@@ -132,10 +197,12 @@ func TestEncodeWithOmitEmpty(t *testing.T) {
132197
String: " ",
133198
Slice: []int{2, 3, 4},
134199
Map: map[string]string{"foo": "bar"},
200+
Time: time.Date(1985, 6, 18, 15, 16, 17, 0, time.UTC),
135201
}
136202
expected := `bool = true
137203
string = " "
138204
slice = [2, 3, 4]
205+
time = 1985-06-18T15:16:17Z
139206
140207
[map]
141208
foo = "bar"

internal/toml-test/json.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build go1.16
2+
// +build go1.16
3+
14
package tomltest
25

36
import (

internal/toml-test/runner_test.go

Lines changed: 0 additions & 38 deletions
This file was deleted.

internal/toml-test/toml.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build go1.16
2+
// +build go1.16
3+
14
package tomltest
25

36
import (

internal/toml-test/version.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build go1.16
2+
// +build go1.16
3+
14
package tomltest
25

36
type versionSpec struct {

0 commit comments

Comments
 (0)