Skip to content

Commit c5fbd3e

Browse files
authored
Add support of mixed-type array (#376)
Fixes #357
1 parent 9ccd9bb commit c5fbd3e

File tree

6 files changed

+122
-50
lines changed

6 files changed

+122
-50
lines changed

marshal.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
475475
return nil, err
476476
}
477477
if e.quoteMapKeys {
478-
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.arraysOneElementPerLine)
478+
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", OrderPreserve, e.arraysOneElementPerLine)
479479
if err != nil {
480480
return nil, err
481481
}
@@ -503,9 +503,6 @@ func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*T
503503

504504
// Convert given marshal slice to slice of toml values
505505
func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
506-
if mtype.Elem().Kind() == reflect.Interface {
507-
return nil, fmt.Errorf("marshal can't handle []interface{}")
508-
}
509506
tval := make([]interface{}, mval.Len(), mval.Len())
510507
for i := 0; i < mval.Len(); i++ {
511508
val, err := e.valueToToml(mtype.Elem(), mval.Index(i))

marshal_test.go

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,10 +2995,6 @@ func TestMarshalInterface(t *testing.T) {
29952995
InterfacePointerField *interface{}
29962996
}
29972997

2998-
type ShouldNotSupportStruct struct {
2999-
InterfaceArray []interface{}
3000-
}
3001-
30022998
expected := []byte(`ArrayField = [1,2,3]
30032999
InterfacePointerField = "hello world"
30043000
PrimitiveField = "string"
@@ -3049,11 +3045,6 @@ PrimitiveField = "string"
30493045
} else {
30503046
t.Fatal(err)
30513047
}
3052-
3053-
// according to the toml standard, data types of array may not be mixed
3054-
if _, err := Marshal(ShouldNotSupportStruct{[]interface{}{1, "a", true}}); err == nil {
3055-
t.Errorf("Should not support []interface{} marshaling")
3056-
}
30573048
}
30583049

30593050
func TestUnmarshalToNilInterface(t *testing.T) {
@@ -3367,6 +3358,77 @@ func TestUnmarshalSliceFail2(t *testing.T) {
33673358

33683359
}
33693360

3361+
func TestMarshalMixedTypeArray(t *testing.T) {
3362+
type InnerStruct struct {
3363+
IntField int
3364+
StrField string
3365+
}
3366+
3367+
type TestStruct struct {
3368+
ArrayField []interface{}
3369+
}
3370+
3371+
expected := []byte(`ArrayField = [3.14,100,true,"hello world",{IntField = 100,StrField = "inner1"},[{IntField = 200,StrField = "inner2"},{IntField = 300,StrField = "inner3"}]]
3372+
`)
3373+
3374+
if result, err := Marshal(TestStruct{
3375+
ArrayField:[]interface{}{
3376+
3.14,
3377+
100,
3378+
true,
3379+
"hello world",
3380+
InnerStruct{
3381+
IntField:100,
3382+
StrField:"inner1",
3383+
},
3384+
[]InnerStruct{
3385+
{IntField:200,StrField:"inner2"},
3386+
{IntField:300,StrField:"inner3"},
3387+
},
3388+
},
3389+
}); err == nil {
3390+
if !bytes.Equal(result, expected) {
3391+
t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result)
3392+
}
3393+
} else {
3394+
t.Fatal(err)
3395+
}
3396+
}
3397+
3398+
func TestUnmarshalMixedTypeArray(t *testing.T) {
3399+
type TestStruct struct {
3400+
ArrayField []interface{}
3401+
}
3402+
3403+
toml := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]]
3404+
`)
3405+
3406+
actual := TestStruct{}
3407+
expected := TestStruct{
3408+
ArrayField:[]interface{}{
3409+
3.14,
3410+
int64(100),
3411+
true,
3412+
"hello world",
3413+
map[string]interface{}{
3414+
"Field":"inner1",
3415+
},
3416+
[]map[string]interface{}{
3417+
{"Field":"inner2"},
3418+
{"Field":"inner3"},
3419+
},
3420+
},
3421+
}
3422+
3423+
if err := Unmarshal(toml, &actual); err == nil {
3424+
if !reflect.DeepEqual(actual, expected) {
3425+
t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual)
3426+
}
3427+
} else {
3428+
t.Fatal(err)
3429+
}
3430+
}
3431+
33703432
func TestUnmarshalArray(t *testing.T) {
33713433
var tree *Tree
33723434
var err error

parser.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ Loop:
427427

428428
func (p *tomlParser) parseArray() interface{} {
429429
var array []interface{}
430-
arrayType := reflect.TypeOf(nil)
430+
arrayType := reflect.TypeOf(newTree())
431431
for {
432432
follow := p.peek()
433433
if follow == nil || follow.typ == tokenEOF {
@@ -438,11 +438,8 @@ func (p *tomlParser) parseArray() interface{} {
438438
break
439439
}
440440
val := p.parseRvalue()
441-
if arrayType == nil {
442-
arrayType = reflect.TypeOf(val)
443-
}
444441
if reflect.TypeOf(val) != arrayType {
445-
p.raiseError(follow, "mixed types in array")
442+
arrayType = nil
446443
}
447444
array = append(array, val)
448445
follow = p.peek()
@@ -456,6 +453,12 @@ func (p *tomlParser) parseArray() interface{} {
456453
p.getToken()
457454
}
458455
}
456+
457+
// if the array is a mixed-type array or its length is 0,
458+
// don't convert it to a table array
459+
if len(array) <= 0 {
460+
arrayType = nil
461+
}
459462
// An array of Trees is actually an array of inline
460463
// tables, which is a shorthand for a table array. If the
461464
// array was not converted from []interface{} to []*Tree,

parser_test.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -488,18 +488,6 @@ func TestNestedEmptyArrays(t *testing.T) {
488488
})
489489
}
490490

491-
func TestArrayMixedTypes(t *testing.T) {
492-
_, err := Load("a = [42, 16.0]")
493-
if err.Error() != "(1, 10): mixed types in array" {
494-
t.Error("Bad error message:", err.Error())
495-
}
496-
497-
_, err = Load("a = [42, \"hello\"]")
498-
if err.Error() != "(1, 11): mixed types in array" {
499-
t.Error("Bad error message:", err.Error())
500-
}
501-
}
502-
503491
func TestArrayNestedStrings(t *testing.T) {
504492
tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]")
505493
assertTree(t, tree, err, map[string]interface{}{
@@ -934,7 +922,7 @@ func TestTomlValueStringRepresentation(t *testing.T) {
934922
{[]interface{}{"gamma", "delta"}, "[\"gamma\",\"delta\"]"},
935923
{nil, ""},
936924
} {
937-
result, err := tomlValueStringRepresentation(item.Value, "", "", false)
925+
result, err := tomlValueStringRepresentation(item.Value, "", "", OrderPreserve, false)
938926
if err != nil {
939927
t.Errorf("Test %d - unexpected error: %s", idx, err)
940928
}

toml_testgen_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,6 @@ import (
55
"testing"
66
)
77

8-
func TestInvalidArrayMixedTypesArraysAndInts(t *testing.T) {
9-
input := `arrays-and-ints = [1, ["Arrays are not integers."]]`
10-
testgenInvalid(t, input)
11-
}
12-
13-
func TestInvalidArrayMixedTypesIntsAndFloats(t *testing.T) {
14-
input := `ints-and-floats = [1, 1.1]`
15-
testgenInvalid(t, input)
16-
}
17-
18-
func TestInvalidArrayMixedTypesStringsAndInts(t *testing.T) {
19-
input := `strings-and-ints = ["hi", 42]`
20-
testgenInvalid(t, input)
21-
}
22-
238
func TestInvalidDatetimeMalformedNoLeads(t *testing.T) {
249
input := `no-leads = 1987-7-05T17:45:00Z`
2510
testgenInvalid(t, input)

tomltree_write.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,42 @@ func encodeTomlString(value string) string {
103103
return b.String()
104104
}
105105

106-
func tomlValueStringRepresentation(v interface{}, commented string, indent string, arraysOneElementPerLine bool) (string, error) {
106+
func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) {
107+
var orderedVals []sortNode
108+
109+
switch ord {
110+
case OrderPreserve:
111+
orderedVals = sortByLines(t)
112+
default:
113+
orderedVals = sortAlphabetical(t)
114+
}
115+
116+
stringBuffer := bytes.Buffer{}
117+
stringBuffer.WriteString(`{`)
118+
first := true
119+
for i := range orderedVals {
120+
v := t.values[orderedVals[i].key]
121+
quotedKey := quoteKeyIfNeeded(orderedVals[i].key)
122+
valueStr, err := tomlValueStringRepresentation(v, "", "", ord, false)
123+
if err != nil {
124+
return "", err
125+
}
126+
if first {
127+
first = false
128+
} else {
129+
stringBuffer.WriteString(`,`)
130+
}
131+
132+
stringBuffer.WriteString(quotedKey)
133+
stringBuffer.WriteString(" = ")
134+
stringBuffer.WriteString(valueStr)
135+
}
136+
stringBuffer.WriteString(`}`)
137+
138+
return stringBuffer.String(), nil
139+
}
140+
141+
func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) {
107142
// this interface check is added to dereference the change made in the writeTo function.
108143
// That change was made to allow this function to see formatting options.
109144
tv, ok := v.(*tomlValue)
@@ -140,7 +175,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
140175
return "\"" + encodeTomlString(value) + "\"", nil
141176
case []byte:
142177
b, _ := v.([]byte)
143-
return tomlValueStringRepresentation(string(b), commented, indent, arraysOneElementPerLine)
178+
return tomlValueStringRepresentation(string(b), commented, indent, ord, arraysOneElementPerLine)
144179
case bool:
145180
if value {
146181
return "true", nil
@@ -154,6 +189,8 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
154189
return value.String(), nil
155190
case LocalTime:
156191
return value.String(), nil
192+
case *Tree:
193+
return tomlTreeStringRepresentation(value, ord)
157194
case nil:
158195
return "", nil
159196
}
@@ -164,7 +201,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
164201
var values []string
165202
for i := 0; i < rv.Len(); i++ {
166203
item := rv.Index(i).Interface()
167-
itemRepr, err := tomlValueStringRepresentation(item, commented, indent, arraysOneElementPerLine)
204+
itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine)
168205
if err != nil {
169206
return "", err
170207
}
@@ -368,7 +405,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
368405
if parentCommented || t.commented || v.commented {
369406
commented = "# "
370407
}
371-
repr, err := tomlValueStringRepresentation(v, commented, indent, arraysOneElementPerLine)
408+
repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine)
372409
if err != nil {
373410
return bytesCount, err
374411
}

0 commit comments

Comments
 (0)