Fix marshaling nested arrays of tables (#395)

Fixes #369
This commit is contained in:
x-hgg-x
2020-05-07 14:09:23 +02:00
committed by GitHub
parent 249d0eaf46
commit 96ff402934
5 changed files with 99 additions and 59 deletions
+2 -2
View File
@@ -367,7 +367,7 @@ func (e *Encoder) PromoteAnonymous(promote bool) *Encoder {
func (e *Encoder) marshal(v interface{}) ([]byte, error) { func (e *Encoder) marshal(v interface{}) ([]byte, error) {
// Check if indentation is valid // Check if indentation is valid
for _, char := range e.indentation { for _, char := range e.indentation {
if !(char == ' ' || char == '\t') { if !isSpace(char) {
return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters") return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters")
} }
} }
@@ -475,7 +475,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
return nil, err return nil, err
} }
if e.quoteMapKeys { if e.quoteMapKeys {
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", OrderPreserve, e.arraysOneElementPerLine) keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+85 -33
View File
@@ -31,7 +31,7 @@ var basicTestData = basicMarshalTestStruct{
SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}}, SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}},
} }
var basicTestToml = []byte(`Ystrlist = ["Howdy","Hey There"] var basicTestToml = []byte(`Ystrlist = ["Howdy", "Hey There"]
Zstring = "Hello" Zstring = "Hello"
[[Wsublist]] [[Wsublist]]
@@ -44,7 +44,7 @@ Zstring = "Hello"
String2 = "One" String2 = "One"
`) `)
var basicTestTomlCustomIndentation = []byte(`Ystrlist = ["Howdy","Hey There"] var basicTestTomlCustomIndentation = []byte(`Ystrlist = ["Howdy", "Hey There"]
Zstring = "Hello" Zstring = "Hello"
[[Wsublist]] [[Wsublist]]
@@ -58,7 +58,7 @@ Zstring = "Hello"
`) `)
var basicTestTomlOrdered = []byte(`Zstring = "Hello" var basicTestTomlOrdered = []byte(`Zstring = "Hello"
Ystrlist = ["Howdy","Hey There"] Ystrlist = ["Howdy", "Hey There"]
[Xsubdoc] [Xsubdoc]
String2 = "One" String2 = "One"
@@ -82,12 +82,12 @@ var marshalTestToml = []byte(`title = "TOML Marshal Testing"
uint = 5001 uint = 5001
[basic_lists] [basic_lists]
bools = [true,false,true] bools = [true, false, true]
dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z]
floats = [12.3,45.6,78.9] floats = [12.3, 45.6, 78.9]
ints = [8001,8001,8002] ints = [8001, 8001, 8002]
strings = ["One","Two","Three"] strings = ["One", "Two", "Three"]
uints = [5002,5003] uints = [5002, 5003]
[basic_map] [basic_map]
one = "one" one = "one"
@@ -114,12 +114,12 @@ var marshalTestToml = []byte(`title = "TOML Marshal Testing"
var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing" var marshalOrderPreserveToml = []byte(`title = "TOML Marshal Testing"
[basic_lists] [basic_lists]
floats = [12.3,45.6,78.9] floats = [12.3, 45.6, 78.9]
bools = [true,false,true] bools = [true, false, true]
dates = [1979-05-27T07:32:00Z,1980-05-27T07:32:00Z] dates = [1979-05-27T07:32:00Z, 1980-05-27T07:32:00Z]
ints = [8001,8001,8002] ints = [8001, 8001, 8002]
uints = [5002,5003] uints = [5002, 5003]
strings = ["One","Two","Three"] strings = ["One", "Two", "Three"]
[[subdocptrs]] [[subdocptrs]]
name = "Second" name = "Second"
@@ -913,8 +913,8 @@ var nestedTestData = nestedMarshalTestStruct{
StringPtr: &strPtr2, StringPtr: &strPtr2,
} }
var nestedTestToml = []byte(`String = [["Five","Six"],["One","Two"]] var nestedTestToml = []byte(`String = [["Five", "Six"], ["One", "Two"]]
StringPtr = [["Three","Four"]] StringPtr = [["Three", "Four"]]
`) `)
func TestNestedMarshal(t *testing.T) { func TestNestedMarshal(t *testing.T) {
@@ -1491,7 +1491,7 @@ type structArrayNoTag struct {
func TestMarshalArray(t *testing.T) { func TestMarshalArray(t *testing.T) {
expected := []byte(` expected := []byte(`
[A] [A]
B = [1,2,3] B = [1, 2, 3]
C = [1] C = [1]
`) `)
@@ -1988,6 +1988,58 @@ func TestMarshalSlicePointer(t *testing.T) {
} }
} }
func TestMarshalNestedArrayInlineTables(t *testing.T) {
type table struct {
Value1 int `toml:"ZValue1"`
Value2 int `toml:"YValue2"`
Value3 int `toml:"XValue3"`
}
type nestedTable struct {
Table table
}
nestedArray := struct {
Simple [][]table
SimplePointer *[]*[]table
Nested [][]nestedTable
NestedPointer *[]*[]nestedTable
}{
Simple: [][]table{{{Value1: 1}, {Value1: 10}}},
SimplePointer: &[]*[]table{{{Value2: 2}}},
Nested: [][]nestedTable{{{Table: table{Value3: 3}}}},
NestedPointer: &[]*[]nestedTable{{{Table: table{Value3: -3}}}},
}
expectedPreserve := `Simple = [[{ ZValue1 = 1, YValue2 = 0, XValue3 = 0 }, { ZValue1 = 10, YValue2 = 0, XValue3 = 0 }]]
SimplePointer = [[{ ZValue1 = 0, YValue2 = 2, XValue3 = 0 }]]
Nested = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = 3 } }]]
NestedPointer = [[{ Table = { ZValue1 = 0, YValue2 = 0, XValue3 = -3 } }]]
`
expectedAlphabetical := `Nested = [[{ Table = { XValue3 = 3, YValue2 = 0, ZValue1 = 0 } }]]
NestedPointer = [[{ Table = { XValue3 = -3, YValue2 = 0, ZValue1 = 0 } }]]
Simple = [[{ XValue3 = 0, YValue2 = 0, ZValue1 = 1 }, { XValue3 = 0, YValue2 = 0, ZValue1 = 10 }]]
SimplePointer = [[{ XValue3 = 0, YValue2 = 2, ZValue1 = 0 }]]
`
var bufPreserve bytes.Buffer
if err := NewEncoder(&bufPreserve).Order(OrderPreserve).Encode(nestedArray); err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if !bytes.Equal(bufPreserve.Bytes(), []byte(expectedPreserve)) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedPreserve, bufPreserve.String())
}
var bufAlphabetical bytes.Buffer
if err := NewEncoder(&bufAlphabetical).Order(OrderAlphabetical).Encode(nestedArray); err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if !bytes.Equal(bufAlphabetical.Bytes(), []byte(expectedAlphabetical)) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedAlphabetical, bufAlphabetical.String())
}
}
type testDuration struct { type testDuration struct {
Nanosec time.Duration `toml:"nanosec"` Nanosec time.Duration `toml:"nanosec"`
Microsec1 time.Duration `toml:"microsec1"` Microsec1 time.Duration `toml:"microsec1"`
@@ -2602,7 +2654,7 @@ func TestMarshalArrays(t *testing.T) {
}{ }{
XY: [2]int{1, 2}, XY: [2]int{1, 2},
}, },
Expected: `XY = [1,2] Expected: `XY = [1, 2]
`, `,
}, },
{ {
@@ -2611,7 +2663,7 @@ func TestMarshalArrays(t *testing.T) {
}{ }{
XY: [1][2]int{{1, 2}}, XY: [1][2]int{{1, 2}},
}, },
Expected: `XY = [[1,2]] Expected: `XY = [[1, 2]]
`, `,
}, },
{ {
@@ -2620,7 +2672,7 @@ func TestMarshalArrays(t *testing.T) {
}{ }{
XY: [1][]int{{1, 2}}, XY: [1][]int{{1, 2}},
}, },
Expected: `XY = [[1,2]] Expected: `XY = [[1, 2]]
`, `,
}, },
{ {
@@ -2629,7 +2681,7 @@ func TestMarshalArrays(t *testing.T) {
}{ }{
XY: [][2]int{{1, 2}}, XY: [][2]int{{1, 2}},
}, },
Expected: `XY = [[1,2]] Expected: `XY = [[1, 2]]
`, `,
}, },
} }
@@ -3047,7 +3099,7 @@ func TestMarshalInterface(t *testing.T) {
InterfacePointerField *interface{} InterfacePointerField *interface{}
} }
expected := []byte(`ArrayField = [1,2,3] expected := []byte(`ArrayField = [1, 2, 3]
InterfacePointerField = "hello world" InterfacePointerField = "hello world"
PrimitiveField = "string" PrimitiveField = "string"
@@ -3420,22 +3472,22 @@ func TestMarshalMixedTypeArray(t *testing.T) {
ArrayField []interface{} ArrayField []interface{}
} }
expected := []byte(`ArrayField = [3.14,100,true,"hello world",{IntField = 100,StrField = "inner1"},[{IntField = 200,StrField = "inner2"},{IntField = 300,StrField = "inner3"}]] expected := []byte(`ArrayField = [3.14, 100, true, "hello world", { IntField = 100, StrField = "inner1" }, [{ IntField = 200, StrField = "inner2" }, { IntField = 300, StrField = "inner3" }]]
`) `)
if result, err := Marshal(TestStruct{ if result, err := Marshal(TestStruct{
ArrayField:[]interface{}{ ArrayField: []interface{}{
3.14, 3.14,
100, 100,
true, true,
"hello world", "hello world",
InnerStruct{ InnerStruct{
IntField:100, IntField: 100,
StrField:"inner1", StrField: "inner1",
}, },
[]InnerStruct{ []InnerStruct{
{IntField:200,StrField:"inner2"}, {IntField: 200, StrField: "inner2"},
{IntField:300,StrField:"inner3"}, {IntField: 300, StrField: "inner3"},
}, },
}, },
}); err == nil { }); err == nil {
@@ -3457,17 +3509,17 @@ func TestUnmarshalMixedTypeArray(t *testing.T) {
actual := TestStruct{} actual := TestStruct{}
expected := TestStruct{ expected := TestStruct{
ArrayField:[]interface{}{ ArrayField: []interface{}{
3.14, 3.14,
int64(100), int64(100),
true, true,
"hello world", "hello world",
map[string]interface{}{ map[string]interface{}{
"Field":"inner1", "Field": "inner1",
}, },
[]map[string]interface{}{ []map[string]interface{}{
{"Field":"inner2"}, {"Field": "inner2"},
{"Field":"inner3"}, {"Field": "inner3"},
}, },
}, },
} }
+2 -2
View File
@@ -919,10 +919,10 @@ func TestTomlValueStringRepresentation(t *testing.T) {
{"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""}, {"\b\t\n\f\r\"\\", "\"\\b\\t\\n\\f\\r\\\"\\\\\""},
{"\x05", "\"\\u0005\""}, {"\x05", "\"\\u0005\""},
{time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "1979-05-27T07:32:00Z"}, {time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), "1979-05-27T07:32:00Z"},
{[]interface{}{"gamma", "delta"}, "[\"gamma\",\"delta\"]"}, {[]interface{}{"gamma", "delta"}, "[\"gamma\", \"delta\"]"},
{nil, ""}, {nil, ""},
} { } {
result, err := tomlValueStringRepresentation(item.Value, "", "", OrderPreserve, false) result, err := tomlValueStringRepresentation(item.Value, "", "", OrderAlphabetical, false)
if err != nil { if err != nil {
t.Errorf("Test %d - unexpected error: %s", idx, err) t.Errorf("Test %d - unexpected error: %s", idx, err)
} }
+1 -1
View File
@@ -105,7 +105,7 @@ func TestTreeCreateToTreeInvalidTableGroupType(t *testing.T) {
} }
func TestRoundTripArrayOfTables(t *testing.T) { func TestRoundTripArrayOfTables(t *testing.T) {
orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\",\"b\"]\n" orig := "\n[[stuff]]\n name = \"foo\"\n things = [\"a\", \"b\"]\n"
tree, err := Load(orig) tree, err := Load(orig)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
+9 -21
View File
@@ -105,7 +105,6 @@ func encodeTomlString(value string) string {
func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) { func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) {
var orderedVals []sortNode var orderedVals []sortNode
switch ord { switch ord {
case OrderPreserve: case OrderPreserve:
orderedVals = sortByLines(t) orderedVals = sortByLines(t)
@@ -113,29 +112,18 @@ func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) {
orderedVals = sortAlphabetical(t) orderedVals = sortAlphabetical(t)
} }
stringBuffer := bytes.Buffer{} var values []string
stringBuffer.WriteString(`{`) for _, node := range orderedVals {
first := true k := node.key
for i := range orderedVals { v := t.values[k]
v := t.values[orderedVals[i].key]
quotedKey := quoteKeyIfNeeded(orderedVals[i].key) repr, err := tomlValueStringRepresentation(v, "", "", ord, false)
valueStr, err := tomlValueStringRepresentation(v, "", "", ord, false)
if err != nil { if err != nil {
return "", err return "", err
} }
if first { values = append(values, quoteKeyIfNeeded(k)+" = "+repr)
first = false
} else {
stringBuffer.WriteString(`,`)
}
stringBuffer.WriteString(quotedKey)
stringBuffer.WriteString(" = ")
stringBuffer.WriteString(valueStr)
} }
stringBuffer.WriteString(`}`) return "{ " + strings.Join(values, ", ") + " }", nil
return stringBuffer.String(), nil
} }
func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) { func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) {
@@ -224,7 +212,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
return stringBuffer.String(), nil return stringBuffer.String(), nil
} }
return "[" + strings.Join(values, ",") + "]", nil return "[" + strings.Join(values, ", ") + "]", nil
} }
return "", fmt.Errorf("unsupported value type %T: %v", v, v) return "", fmt.Errorf("unsupported value type %T: %v", v, v)
} }