Order map keys alphabetically (#270)
This makes sure we have a stable output when marshaling maps. Fixes #268
This commit is contained in:
committed by
Thomas Pelletier
parent
6ea91ef590
commit
65b27e6823
+21
-1
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -329,7 +330,26 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range mval.MapKeys() {
|
||||
keys := mval.MapKeys()
|
||||
if e.order == OrderPreserve && len(keys) > 0 {
|
||||
// Sorting []reflect.Value is not straight forward.
|
||||
//
|
||||
// OrderPreserve will support deterministic results when string is used
|
||||
// as the key to maps.
|
||||
typ := keys[0].Type()
|
||||
kind := keys[0].Kind()
|
||||
if kind == reflect.String {
|
||||
ikeys := make([]string, len(keys))
|
||||
for i := range keys {
|
||||
ikeys[i] = keys[i].Interface().(string)
|
||||
}
|
||||
sort.Strings(ikeys)
|
||||
for i := range ikeys {
|
||||
keys[i] = reflect.ValueOf(ikeys[i]).Convert(typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, key := range keys {
|
||||
mvalf := mval.MapIndex(key)
|
||||
val, err := e.valueToToml(mtype.Elem(), mvalf)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
title = "TOML Marshal Testing"
|
||||
|
||||
[basic_map]
|
||||
one = "one"
|
||||
two = "two"
|
||||
|
||||
[long_map]
|
||||
a7 = "1"
|
||||
b3 = "2"
|
||||
c8 = "3"
|
||||
d4 = "4"
|
||||
e6 = "5"
|
||||
f5 = "6"
|
||||
g10 = "7"
|
||||
h1 = "8"
|
||||
i2 = "9"
|
||||
j9 = "10"
|
||||
@@ -126,6 +126,12 @@ type testDoc struct {
|
||||
Unexported2 int `toml:"-"`
|
||||
}
|
||||
|
||||
type testMapDoc struct {
|
||||
Title string `toml:"title"`
|
||||
BasicMap map[string]string `toml:"basic_map"`
|
||||
LongMap map[string]string `toml:"long_map"`
|
||||
}
|
||||
|
||||
type testDocBasics struct {
|
||||
Uint uint `toml:"uint"`
|
||||
Bool bool `toml:"bool"`
|
||||
@@ -200,6 +206,26 @@ var docData = testDoc{
|
||||
SubDocPtrs: []*testSubDoc{&subdoc},
|
||||
}
|
||||
|
||||
var mapTestDoc = testMapDoc{
|
||||
Title: "TOML Marshal Testing",
|
||||
BasicMap: map[string]string{
|
||||
"one": "one",
|
||||
"two": "two",
|
||||
},
|
||||
LongMap: map[string]string{
|
||||
"h1": "8",
|
||||
"i2": "9",
|
||||
"b3": "2",
|
||||
"d4": "4",
|
||||
"f5": "6",
|
||||
"e6": "5",
|
||||
"a7": "1",
|
||||
"c8": "3",
|
||||
"j9": "10",
|
||||
"g10": "7",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDocMarshal(t *testing.T) {
|
||||
result, err := Marshal(docData)
|
||||
if err != nil {
|
||||
@@ -223,6 +249,29 @@ func TestDocMarshalOrdered(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocMarshalMaps(t *testing.T) {
|
||||
result, err := Marshal(mapTestDoc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected, _ := ioutil.ReadFile("marshal_OrderPreserve_Map_test.toml")
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocMarshalOrderedMaps(t *testing.T) {
|
||||
var result bytes.Buffer
|
||||
err := NewEncoder(&result).Order(OrderPreserve).Encode(mapTestDoc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected, _ := ioutil.ReadFile("marshal_OrderPreserve_Map_test.toml")
|
||||
if !bytes.Equal(result.Bytes(), expected) {
|
||||
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocMarshalPointer(t *testing.T) {
|
||||
result, err := Marshal(&docData)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user