diff --git a/marshal.go b/marshal.go index 278608a..230a432 100644 --- a/marshal.go +++ b/marshal.go @@ -475,7 +475,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er return nil, err } if e.quoteMapKeys { - keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.arraysOneElementPerLine) + keyStr, err := tomlValueStringRepresentation(key.String(), "", "", OrderPreserve, e.arraysOneElementPerLine) if err != nil { return nil, err } @@ -503,9 +503,6 @@ func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*T // Convert given marshal slice to slice of toml values func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { - if mtype.Elem().Kind() == reflect.Interface { - return nil, fmt.Errorf("marshal can't handle []interface{}") - } tval := make([]interface{}, mval.Len(), mval.Len()) for i := 0; i < mval.Len(); i++ { val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) diff --git a/marshal_test.go b/marshal_test.go index 64dfa76..2757ad5 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -2995,10 +2995,6 @@ func TestMarshalInterface(t *testing.T) { InterfacePointerField *interface{} } - type ShouldNotSupportStruct struct { - InterfaceArray []interface{} - } - expected := []byte(`ArrayField = [1,2,3] InterfacePointerField = "hello world" PrimitiveField = "string" @@ -3049,11 +3045,6 @@ PrimitiveField = "string" } else { t.Fatal(err) } - - // according to the toml standard, data types of array may not be mixed - if _, err := Marshal(ShouldNotSupportStruct{[]interface{}{1, "a", true}}); err == nil { - t.Errorf("Should not support []interface{} marshaling") - } } func TestUnmarshalToNilInterface(t *testing.T) { @@ -3367,6 +3358,77 @@ func TestUnmarshalSliceFail2(t *testing.T) { } +func TestMarshalMixedTypeArray(t *testing.T) { + type InnerStruct struct { + IntField int + StrField string + } + + type TestStruct struct { + ArrayField []interface{} + } + + 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{ + ArrayField:[]interface{}{ + 3.14, + 100, + true, + "hello world", + InnerStruct{ + IntField:100, + StrField:"inner1", + }, + []InnerStruct{ + {IntField:200,StrField:"inner2"}, + {IntField:300,StrField:"inner3"}, + }, + }, + }); err == nil { + if !bytes.Equal(result, expected) { + t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result) + } + } else { + t.Fatal(err) + } +} + +func TestUnmarshalMixedTypeArray(t *testing.T) { + type TestStruct struct { + ArrayField []interface{} + } + + toml := []byte(`ArrayField = [3.14,100,true,"hello world",{Field = "inner1"},[{Field = "inner2"},{Field = "inner3"}]] +`) + + actual := TestStruct{} + expected := TestStruct{ + ArrayField:[]interface{}{ + 3.14, + int64(100), + true, + "hello world", + map[string]interface{}{ + "Field":"inner1", + }, + []map[string]interface{}{ + {"Field":"inner2"}, + {"Field":"inner3"}, + }, + }, + } + + if err := Unmarshal(toml, &actual); err == nil { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Bad unmarshal: expected %#v, got %#v", expected, actual) + } + } else { + t.Fatal(err) + } +} + func TestUnmarshalArray(t *testing.T) { var tree *Tree var err error diff --git a/parser.go b/parser.go index 3da5677..7bf40bb 100644 --- a/parser.go +++ b/parser.go @@ -427,7 +427,7 @@ Loop: func (p *tomlParser) parseArray() interface{} { var array []interface{} - arrayType := reflect.TypeOf(nil) + arrayType := reflect.TypeOf(newTree()) for { follow := p.peek() if follow == nil || follow.typ == tokenEOF { @@ -438,11 +438,8 @@ func (p *tomlParser) parseArray() interface{} { break } val := p.parseRvalue() - if arrayType == nil { - arrayType = reflect.TypeOf(val) - } if reflect.TypeOf(val) != arrayType { - p.raiseError(follow, "mixed types in array") + arrayType = nil } array = append(array, val) follow = p.peek() @@ -456,6 +453,12 @@ func (p *tomlParser) parseArray() interface{} { p.getToken() } } + + // if the array is a mixed-type array or its length is 0, + // don't convert it to a table array + if len(array) <= 0 { + arrayType = nil + } // An array of Trees is actually an array of inline // tables, which is a shorthand for a table array. If the // array was not converted from []interface{} to []*Tree, diff --git a/parser_test.go b/parser_test.go index 5c81f2b..56f48a9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -488,18 +488,6 @@ func TestNestedEmptyArrays(t *testing.T) { }) } -func TestArrayMixedTypes(t *testing.T) { - _, err := Load("a = [42, 16.0]") - if err.Error() != "(1, 10): mixed types in array" { - t.Error("Bad error message:", err.Error()) - } - - _, err = Load("a = [42, \"hello\"]") - if err.Error() != "(1, 11): mixed types in array" { - t.Error("Bad error message:", err.Error()) - } -} - func TestArrayNestedStrings(t *testing.T) { tree, err := Load("data = [ [\"gamma\", \"delta\"], [\"Foo\"] ]") assertTree(t, tree, err, map[string]interface{}{ @@ -934,7 +922,7 @@ func TestTomlValueStringRepresentation(t *testing.T) { {[]interface{}{"gamma", "delta"}, "[\"gamma\",\"delta\"]"}, {nil, ""}, } { - result, err := tomlValueStringRepresentation(item.Value, "", "", false) + result, err := tomlValueStringRepresentation(item.Value, "", "", OrderPreserve, false) if err != nil { t.Errorf("Test %d - unexpected error: %s", idx, err) } diff --git a/toml_testgen_test.go b/toml_testgen_test.go index 688ae51..2306926 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -5,21 +5,6 @@ import ( "testing" ) -func TestInvalidArrayMixedTypesArraysAndInts(t *testing.T) { - input := `arrays-and-ints = [1, ["Arrays are not integers."]]` - testgenInvalid(t, input) -} - -func TestInvalidArrayMixedTypesIntsAndFloats(t *testing.T) { - input := `ints-and-floats = [1, 1.1]` - testgenInvalid(t, input) -} - -func TestInvalidArrayMixedTypesStringsAndInts(t *testing.T) { - input := `strings-and-ints = ["hi", 42]` - testgenInvalid(t, input) -} - func TestInvalidDatetimeMalformedNoLeads(t *testing.T) { input := `no-leads = 1987-7-05T17:45:00Z` testgenInvalid(t, input) diff --git a/tomltree_write.go b/tomltree_write.go index 5d2faff..43d195b 100644 --- a/tomltree_write.go +++ b/tomltree_write.go @@ -103,7 +103,42 @@ func encodeTomlString(value string) string { return b.String() } -func tomlValueStringRepresentation(v interface{}, commented string, indent string, arraysOneElementPerLine bool) (string, error) { +func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) { + var orderedVals []sortNode + + switch ord { + case OrderPreserve: + orderedVals = sortByLines(t) + default: + orderedVals = sortAlphabetical(t) + } + + stringBuffer := bytes.Buffer{} + stringBuffer.WriteString(`{`) + first := true + for i := range orderedVals { + v := t.values[orderedVals[i].key] + quotedKey := quoteKeyIfNeeded(orderedVals[i].key) + valueStr, err := tomlValueStringRepresentation(v, "", "", ord, false) + if err != nil { + return "", err + } + if first { + first = false + } else { + stringBuffer.WriteString(`,`) + } + + stringBuffer.WriteString(quotedKey) + stringBuffer.WriteString(" = ") + stringBuffer.WriteString(valueStr) + } + stringBuffer.WriteString(`}`) + + return stringBuffer.String(), nil +} + +func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) { // this interface check is added to dereference the change made in the writeTo function. // That change was made to allow this function to see formatting options. tv, ok := v.(*tomlValue) @@ -140,7 +175,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin return "\"" + encodeTomlString(value) + "\"", nil case []byte: b, _ := v.([]byte) - return tomlValueStringRepresentation(string(b), commented, indent, arraysOneElementPerLine) + return tomlValueStringRepresentation(string(b), commented, indent, ord, arraysOneElementPerLine) case bool: if value { return "true", nil @@ -154,6 +189,8 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin return value.String(), nil case LocalTime: return value.String(), nil + case *Tree: + return tomlTreeStringRepresentation(value, ord) case nil: return "", nil } @@ -164,7 +201,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin var values []string for i := 0; i < rv.Len(); i++ { item := rv.Index(i).Interface() - itemRepr, err := tomlValueStringRepresentation(item, commented, indent, arraysOneElementPerLine) + itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine) if err != nil { return "", err } @@ -368,7 +405,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i if parentCommented || t.commented || v.commented { commented = "# " } - repr, err := tomlValueStringRepresentation(v, commented, indent, arraysOneElementPerLine) + repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine) if err != nil { return bytesCount, err }