diff --git a/README.md b/README.md
index b2f3312a..5a03665a 100644
--- a/README.md
+++ b/README.md
@@ -156,14 +156,17 @@ Execution time speedup compared to other Go TOML libraries:
See more
-The table above has the results of the most common use-cases. The table
-below contains the results of all benchmarks, including unrealistic ones. is
+
The table above has the results of the most common use-cases. The table below
+contains the results of all benchmarks, including unrealistic ones. It is
provided for completeness.
@@ -171,14 +174,16 @@ provided for completeness.
Benchmark | go-toml v1 | BurntSushi/toml |
- UnmarshalSimple/map | 3.8x | 2.4x |
- UnmarshalSimple/struct | 5.4x | 3.1x |
- UnmarshalDataset/example | 2.8x | 2.0x |
- UnmarshalDataset/code | 1.8x | 2.2x |
- UnmarshalDataset/twitter | 2.5x | 1.8x |
- UnmarshalDataset/citm_catalog | 1.9x | 1.2x |
- UnmarshalDataset/config | 3.0x | 2.5x |
- [Geo mean] | 3.0x | 2.4x |
+ Marshal/SimpleDocument/map | 1.8x | 2.4x |
+ Marshal/SimpleDocument/struct | 2.7x | 3.5x |
+ Unmarshal/SimpleDocument/map | 4.3x | 2.4x |
+ Unmarshal/SimpleDocument/struct | 5.8x | 3.3x |
+ UnmarshalDataset/example | 3.1x | 2.2x |
+ UnmarshalDataset/code | 1.8x | 2.1x |
+ UnmarshalDataset/twitter | 2.7x | 1.9x |
+ UnmarshalDataset/citm_catalog | 1.8x | 1.2x |
+ UnmarshalDataset/config | 3.4x | 2.8x |
+ [Geo mean] | 2.8x | 2.5x |
This table can be generated with ./ci.sh benchmark -a -html
.
diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go
index 6d90571b..a51476cd 100644
--- a/benchmark/benchmark_test.go
+++ b/benchmark/benchmark_test.go
@@ -1,6 +1,7 @@
package benchmark_test
import (
+ "bytes"
"io/ioutil"
"testing"
"time"
@@ -21,15 +22,101 @@ func TestUnmarshalSimple(t *testing.T) {
}
}
-func BenchmarkUnmarshalSimple(b *testing.B) {
- doc := []byte(`A = "hello"`)
+func BenchmarkUnmarshal(b *testing.B) {
+ b.Run("SimpleDocument", func(b *testing.B) {
+ doc := []byte(`A = "hello"`)
+
+ b.Run("struct", func(b *testing.B) {
+ b.SetBytes(int64(len(doc)))
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ d := struct {
+ A string
+ }{}
+
+ err := toml.Unmarshal(doc, &d)
+ if err != nil {
+ panic(err)
+ }
+ }
+ })
+
+ b.Run("map", func(b *testing.B) {
+ b.SetBytes(int64(len(doc)))
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(doc, &d)
+ if err != nil {
+ panic(err)
+ }
+ }
+ })
+ })
+
+ b.Run("ReferenceFile", func(b *testing.B) {
+ bytes, err := ioutil.ReadFile("benchmark.toml")
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.Run("struct", func(b *testing.B) {
+ b.SetBytes(int64(len(bytes)))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ d := benchmarkDoc{}
+ err := toml.Unmarshal(bytes, &d)
+ if err != nil {
+ panic(err)
+ }
+ }
+ })
+
+ b.Run("map", func(b *testing.B) {
+ b.SetBytes(int64(len(bytes)))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(bytes, &d)
+ if err != nil {
+ panic(err)
+ }
+ }
+ })
+ })
- b.Run("struct", func(b *testing.B) {
- b.SetBytes(int64(len(doc)))
+ b.Run("HugoFrontMatter", func(b *testing.B) {
+ b.SetBytes(int64(len(hugoFrontMatterbytes)))
b.ReportAllocs()
b.ResetTimer()
-
for i := 0; i < b.N; i++ {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(hugoFrontMatterbytes, &d)
+ if err != nil {
+ panic(err)
+ }
+ }
+ })
+}
+
+func marshal(v interface{}) ([]byte, error) {
+ var b bytes.Buffer
+ enc := toml.NewEncoder(&b)
+ err := enc.Encode(v)
+ return b.Bytes(), err
+}
+
+func BenchmarkMarshal(b *testing.B) {
+ b.Run("SimpleDocument", func(b *testing.B) {
+ doc := []byte(`A = "hello"`)
+
+ b.Run("struct", func(b *testing.B) {
d := struct {
A string
}{}
@@ -38,21 +125,114 @@ func BenchmarkUnmarshalSimple(b *testing.B) {
if err != nil {
panic(err)
}
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ var out []byte
+
+ for i := 0; i < b.N; i++ {
+ out, err = marshal(d)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ b.SetBytes(int64(len(out)))
+ })
+
+ b.Run("map", func(b *testing.B) {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(doc, &d)
+ if err != nil {
+ panic(err)
+ }
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ var out []byte
+
+ for i := 0; i < b.N; i++ {
+ out, err = marshal(d)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ b.SetBytes(int64(len(out)))
+ })
+ })
+
+ b.Run("ReferenceFile", func(b *testing.B) {
+ bytes, err := ioutil.ReadFile("benchmark.toml")
+ if err != nil {
+ b.Fatal(err)
}
+
+ b.Run("struct", func(b *testing.B) {
+ d := benchmarkDoc{}
+ err := toml.Unmarshal(bytes, &d)
+ if err != nil {
+ panic(err)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ var out []byte
+
+ for i := 0; i < b.N; i++ {
+ out, err = marshal(d)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ b.SetBytes(int64(len(out)))
+ })
+
+ b.Run("map", func(b *testing.B) {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(bytes, &d)
+ if err != nil {
+ panic(err)
+ }
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ var out []byte
+ for i := 0; i < b.N; i++ {
+ out, err = marshal(d)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ b.SetBytes(int64(len(out)))
+ })
})
- b.Run("map", func(b *testing.B) {
- b.SetBytes(int64(len(doc)))
+ b.Run("HugoFrontMatter", func(b *testing.B) {
+ d := map[string]interface{}{}
+ err := toml.Unmarshal(hugoFrontMatterbytes, &d)
+ if err != nil {
+ panic(err)
+ }
+
b.ReportAllocs()
b.ResetTimer()
+ var out []byte
+
for i := 0; i < b.N; i++ {
- d := map[string]interface{}{}
- err := toml.Unmarshal(doc, &d)
+ out, err = marshal(d)
if err != nil {
panic(err)
}
}
+
+ b.SetBytes(int64(len(out)))
})
}
@@ -163,40 +343,7 @@ type benchmarkDoc struct {
}
}
-func BenchmarkReferenceFile(b *testing.B) {
- bytes, err := ioutil.ReadFile("benchmark.toml")
- if err != nil {
- b.Fatal(err)
- }
-
- b.Run("struct", func(b *testing.B) {
- b.SetBytes(int64(len(bytes)))
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- d := benchmarkDoc{}
- err := toml.Unmarshal(bytes, &d)
- if err != nil {
- panic(err)
- }
- }
- })
-
- b.Run("map", func(b *testing.B) {
- b.SetBytes(int64(len(bytes)))
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- d := map[string]interface{}{}
- err := toml.Unmarshal(bytes, &d)
- if err != nil {
- panic(err)
- }
- }
- })
-}
-
-func TestReferenceFile(t *testing.T) {
+func TestUnmarshalReferenceFile(t *testing.T) {
bytes, err := ioutil.ReadFile("benchmark.toml")
require.NoError(t, err)
d := benchmarkDoc{}
@@ -483,8 +630,7 @@ trimmed in raw strings.
require.Equal(t, expected, d)
}
-func BenchmarkHugoFrontMatter(b *testing.B) {
- bytes := []byte(`
+var hugoFrontMatterbytes = []byte(`
categories = ["Development", "VIM"]
date = "2012-04-06"
description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
@@ -506,14 +652,3 @@ show_comments = false
[cascade._target]
kind = "section"
`)
- b.SetBytes(int64(len(bytes)))
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- d := map[string]interface{}{}
- err := toml.Unmarshal(bytes, &d)
- if err != nil {
- panic(err)
- }
- }
-}
diff --git a/ci.sh b/ci.sh
index 22cda0ad..3918f531 100755
--- a/ci.sh
+++ b/ci.sh
@@ -204,11 +204,18 @@ def printtable(data):
""")
-fold = 3
-printtable(results[:fold])
+def match(x):
+ return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0]
+
+above = [x for x in results if match(x)]
+below = [x for x in results if not match(x)]
+
+printtable(above)
print("See more
")
-print('The table above has the results of the most common use-cases. The table below contains the results of all benchmarks, including unrealistic ones. is provided for completeness.
')
-printtable(results[fold:])
+print("""The table above has the results of the most common use-cases. The table below
+contains the results of all benchmarks, including unrealistic ones. It is
+provided for completeness.
""")
+printtable(below)
print('This table can be generated with ./ci.sh benchmark -a -html
.
')
print(" ")
diff --git a/marshaler.go b/marshaler.go
index 0d8ed9c5..3d87dab6 100644
--- a/marshaler.go
+++ b/marshaler.go
@@ -641,6 +641,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
}
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
+ if !v.IsValid() {
+ return false
+ }
if v.Type() == timeType || v.Type().Implements(textMarshalerType) {
return false
}
diff --git a/unmarshaler.go b/unmarshaler.go
index 6dcb5c5c..a3280454 100644
--- a/unmarshaler.go
+++ b/unmarshaler.go
@@ -290,7 +290,11 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
return v, nil
case reflect.Slice:
- elem := reflect.New(v.Type().Elem()).Elem()
+ elemType := v.Type().Elem()
+ if elemType.Kind() == reflect.Interface {
+ elemType = mapStringInterfaceType
+ }
+ elem := reflect.New(elemType).Elem()
elem2, err := d.handleArrayTable(key, elem)
if err != nil {
return reflect.Value{}, err
diff --git a/unmarshaler_test.go b/unmarshaler_test.go
index f3deb238..e14c9be0 100644
--- a/unmarshaler_test.go
+++ b/unmarshaler_test.go
@@ -688,7 +688,7 @@ B = "data"`,
"Name": "Hammer",
"Sku": int64(738594937),
},
- nil,
+ map[string]interface{}(nil),
map[string]interface{}{
"Name": "Nail",
"Sku": int64(284758393),
@@ -1201,6 +1201,20 @@ B = "data"`,
}
},
},
+ {
+ desc: "empty array table in interface{}",
+ input: `[[products]]`,
+ gen: func() test {
+ return test{
+ target: &map[string]interface{}{},
+ expected: &map[string]interface{}{
+ "products": []interface{}{
+ map[string]interface{}(nil),
+ },
+ },
+ }
+ },
+ },
{
desc: "into map with invalid key type",
input: `A = "hello"`,