Skip to content

Commit

Permalink
Marshal: define and fix newlines behavior when using omitempty (#798)
Browse files Browse the repository at this point in the history
Ref #786
  • Loading branch information
pelletier authored Jul 24, 2022
1 parent d017a6d commit fb6d1d6
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 128 deletions.
1 change: 0 additions & 1 deletion cmd/jsontoml/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func TestConvert(t *testing.T) {
}`,
expected: `[mytoml]
a = 42.0
`,
},
{
Expand Down
1 change: 0 additions & 1 deletion cmd/tomll/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ mytoml.a = 42.0
`,
expected: `[mytoml]
a = 42.0
`,
},
{
Expand Down
9 changes: 5 additions & 4 deletions internal/imported_tests/marshal_imported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestDocMarshal(t *testing.T) {
}

marshalTestToml := `title = 'TOML Marshal Testing'
[basic_lists]
floats = [12.3, 45.6, 78.9]
bools = [true, false, true]
Expand All @@ -89,7 +90,6 @@ name = 'Second'
[subdoc.first]
name = 'First'
[basic]
uint = 5001
bool = true
Expand All @@ -101,9 +101,9 @@ date = 1979-05-27T07:32:00Z
[[subdoclist]]
name = 'List.First'
[[subdoclist]]
name = 'List.Second'
`

result, err := toml.Marshal(docData)
Expand All @@ -117,14 +117,15 @@ func TestBasicMarshalQuotedKey(t *testing.T) {

expected := `'Z.string-àéù' = 'Hello'
'Yfloat-𝟘' = 3.5
['Xsubdoc-àéù']
String2 = 'One'
[['W.sublist-𝟘']]
String2 = 'Two'
[['W.sublist-𝟘']]
String2 = 'Three'
`

require.Equal(t, string(expected), string(result))
Expand Down Expand Up @@ -159,8 +160,8 @@ bool = false
int = 0
string = ''
stringlist = []
[map]
[map]
`

require.Equal(t, string(expected), string(result))
Expand Down
85 changes: 76 additions & 9 deletions marshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewEncoder(w io.Writer) *Encoder {
// This behavior can be controlled on an individual struct field basis with the
// inline tag:
//
// MyField `inline:"true"`
// MyField `toml:",inline"`
func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
enc.tablesInline = inline
return enc
Expand Down Expand Up @@ -117,6 +117,19 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// When encoding structs, fields are encoded in order of definition, with their
// exact name.
//
// Tables and array tables are separated by empty lines. However, consecutive
// subtables definitions are not. For example:
//
// [top1]
//
// [top2]
// [top2.child1]
//
// [[array]]
//
// [[array]]
// [array.child2]
//
// Struct tags
//
// The encoding of each public struct field can be customized by the format
Expand Down Expand Up @@ -333,13 +346,13 @@ func isNil(v reflect.Value) bool {
}
}

func shouldOmitEmpty(ctx encoderCtx, options valueOptions, v reflect.Value) bool {
return (ctx.options.omitempty || options.omitempty) && isEmptyValue(v)
}

func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
var err error

if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
return b, nil
}

if !ctx.inline {
b = enc.encodeComment(ctx.indent, options.comment, b)
}
Expand All @@ -365,6 +378,8 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r

func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Struct:
return isEmptyStruct(v)
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
Expand All @@ -381,6 +396,34 @@ func isEmptyValue(v reflect.Value) bool {
return false
}

func isEmptyStruct(v reflect.Value) bool {
// TODO: merge with walkStruct and cache.
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)

// only consider exported fields
if fieldType.PkgPath != "" {
continue
}

tag := fieldType.Tag.Get("toml")

// special field name to skip field
if tag == "-" {
continue
}

f := v.Field(i)

if !isEmptyValue(f) {
return false
}
}

return true
}

const literalQuote = '\''

func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
Expand Down Expand Up @@ -410,7 +453,6 @@ func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
return b
}

//nolint:cyclop
func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
stringQuote := `"`

Expand Down Expand Up @@ -757,7 +799,13 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
}
ctx.skipTableHeader = false

hasNonEmptyKV := false
for _, kv := range t.kvs {
if shouldOmitEmpty(ctx, kv.Options, kv.Value) {
continue
}
hasNonEmptyKV = true

ctx.setKey(kv.Key)

b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
Expand All @@ -768,7 +816,20 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
b = append(b, '\n')
}

first := true
for _, table := range t.tables {
if shouldOmitEmpty(ctx, table.Options, table.Value) {
continue
}
if first {
first = false
if hasNonEmptyKV {
b = append(b, '\n')
}
} else {
b = append(b, "\n"...)
}

ctx.setKey(table.Key)

ctx.options = table.Options
Expand All @@ -777,8 +838,6 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
if err != nil {
return nil, err
}

b = append(b, '\n')
}

return b, nil
Expand All @@ -791,6 +850,10 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte

first := true
for _, kv := range t.kvs {
if shouldOmitEmpty(ctx, kv.Options, kv.Value) {
continue
}

if first {
first = false
} else {
Expand All @@ -806,7 +869,7 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
}

if len(t.tables) > 0 {
panic("inline table cannot contain nested tables, online key-values")
panic("inline table cannot contain nested tables, only key-values")
}

b = append(b, "}"...)
Expand Down Expand Up @@ -905,6 +968,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)

for i := 0; i < v.Len(); i++ {
if i != 0 {
b = append(b, "\n"...)
}

b = append(b, scratch...)

var err error
Expand Down
Loading

0 comments on commit fb6d1d6

Please sign in to comment.