encode: respect stdlib rules for embedded structs (#747)
This commit is contained in:
@@ -527,42 +527,14 @@ The new name is `Encoder.SetArraysMultiline`. The behavior should be the same.
|
|||||||
The new name is `Encoder.SetIndentSymbol`. The behavior should be the same.
|
The new name is `Encoder.SetIndentSymbol`. The behavior should be the same.
|
||||||
|
|
||||||
|
|
||||||
#### Embedded structs are tables
|
#### Embedded structs behave like stdlib
|
||||||
|
|
||||||
V1 defaults to merging embedded struct fields into the embedding struct. This
|
V1 defaults to merging embedded struct fields into the embedding struct. This
|
||||||
behavior was unexpected because it does not follow the standard library. To
|
behavior was unexpected because it does not follow the standard library. To
|
||||||
avoid breaking backward compatibility, the `Encoder.PromoteAnonymous` method was
|
avoid breaking backward compatibility, the `Encoder.PromoteAnonymous` method was
|
||||||
added to make the encoder behave correctly. Given backward compatibility is not
|
added to make the encoder behave correctly. Given backward compatibility is not
|
||||||
a problem anymore, v2 does the right thing by default. There is no way to revert
|
a problem anymore, v2 does the right thing by default: it follows the behavior
|
||||||
to the old behavior, and `Encoder.PromoteAnonymous` has been removed.
|
of `encoding/json`. `Encoder.PromoteAnonymous` has been removed.
|
||||||
|
|
||||||
```go
|
|
||||||
type Embedded struct {
|
|
||||||
Value string `toml:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Doc struct {
|
|
||||||
Embedded
|
|
||||||
}
|
|
||||||
|
|
||||||
d := Doc{}
|
|
||||||
|
|
||||||
fmt.Println("v1:")
|
|
||||||
b, err := v1.Marshal(d)
|
|
||||||
fmt.Println(string(b))
|
|
||||||
|
|
||||||
fmt.Println("v2:")
|
|
||||||
b, err = v2.Marshal(d)
|
|
||||||
fmt.Println(string(b))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// v1:
|
|
||||||
// value = ""
|
|
||||||
//
|
|
||||||
// v2:
|
|
||||||
// [Embedded]
|
|
||||||
// value = ''
|
|
||||||
```
|
|
||||||
|
|
||||||
[nodoc]: https://github.com/pelletier/go-toml/discussions/506#discussioncomment-1526038
|
[nodoc]: https://github.com/pelletier/go-toml/discussions/506#discussioncomment-1526038
|
||||||
|
|
||||||
|
|||||||
+30
-8
@@ -555,16 +555,25 @@ type table struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
|
func (t *table) pushKV(k string, v reflect.Value, options valueOptions) {
|
||||||
|
for _, e := range t.kvs {
|
||||||
|
if e.Key == k {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
|
t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
|
func (t *table) pushTable(k string, v reflect.Value, options valueOptions) {
|
||||||
|
for _, e := range t.tables {
|
||||||
|
if e.Key == k {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
|
t.tables = append(t.tables, entry{Key: k, Value: v, Options: options})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
||||||
var t table
|
|
||||||
|
|
||||||
// TODO: cache this
|
// TODO: cache this
|
||||||
typ := v.Type()
|
typ := v.Type()
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
@@ -575,8 +584,6 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
k := fieldType.Name
|
|
||||||
|
|
||||||
tag := fieldType.Tag.Get("toml")
|
tag := fieldType.Tag.Get("toml")
|
||||||
|
|
||||||
// special field name to skip field
|
// special field name to skip field
|
||||||
@@ -584,13 +591,22 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name, opts := parseTag(tag)
|
k, opts := parseTag(tag)
|
||||||
if isValidName(name) {
|
if !isValidName(k) {
|
||||||
k = name
|
k = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
f := v.Field(i)
|
f := v.Field(i)
|
||||||
|
|
||||||
|
if k == "" {
|
||||||
|
if fieldType.Anonymous {
|
||||||
|
walkStruct(ctx, t, f)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
k = fieldType.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if isNil(f) {
|
if isNil(f) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -607,6 +623,12 @@ func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]b
|
|||||||
t.pushTable(k, f, options)
|
t.pushTable(k, f, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
|
var t table
|
||||||
|
|
||||||
|
walkStruct(ctx, &t, v)
|
||||||
|
|
||||||
return enc.encodeTable(b, ctx, t)
|
return enc.encodeTable(b, ctx, t)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -947,6 +947,70 @@ func TestIssue678(t *testing.T) {
|
|||||||
require.Equal(t, cfg, cfg2)
|
require.Equal(t, cfg, cfg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalNestedAnonymousStructs(t *testing.T) {
|
||||||
|
type Embedded struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
Top struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
} `toml:"top" json:"top"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Named struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc struct {
|
||||||
|
Embedded
|
||||||
|
Named `toml:"named" json:"named"`
|
||||||
|
Anonymous struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
} `toml:"anonymous" json:"anonymous"`
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `value = ''
|
||||||
|
[top]
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
[named]
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
[anonymous]
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
result, err := toml.Marshal(doc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalNestedAnonymousStructs_DuplicateField(t *testing.T) {
|
||||||
|
type Embedded struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
Top struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
} `toml:"top" json:"top"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc struct {
|
||||||
|
Value string `toml:"value" json:"value"`
|
||||||
|
Embedded
|
||||||
|
}
|
||||||
|
doc.Embedded.Value = "shadowed"
|
||||||
|
doc.Value = "shadows"
|
||||||
|
|
||||||
|
expected := `value = 'shadows'
|
||||||
|
[top]
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
result, err := toml.Marshal(doc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(result))
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleMarshal() {
|
func ExampleMarshal() {
|
||||||
type MyConfig struct {
|
type MyConfig struct {
|
||||||
Version int
|
Version int
|
||||||
|
|||||||
Reference in New Issue
Block a user