Skip to content

Commit d553047

Browse files
Allow int, uint, and floats as map keys (#958)
Signed-off-by: Daniel Weiße <[email protected]>
1 parent 0977c05 commit d553047

File tree

4 files changed

+251
-22
lines changed

4 files changed

+251
-22
lines changed

marshaler.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,18 @@ func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
631631
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
632632
}
633633
return string(keyB), nil
634+
635+
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
636+
return strconv.FormatInt(k.Int(), 10), nil
637+
638+
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
639+
return strconv.FormatUint(k.Uint(), 10), nil
640+
641+
case keyType.Kind() == reflect.Float32:
642+
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
643+
644+
case keyType.Kind() == reflect.Float64:
645+
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
634646
}
635647
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
636648
}

marshaler_test.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,13 +587,69 @@ foo = 42
587587
`,
588588
},
589589
{
590-
desc: "invalid map key",
590+
desc: "int map key",
591591
v: map[int]interface{}{1: "a"},
592+
expected: `1 = 'a'
593+
`,
594+
},
595+
{
596+
desc: "int8 map key",
597+
v: map[int8]interface{}{1: "a"},
598+
expected: `1 = 'a'
599+
`,
600+
},
601+
{
602+
desc: "int64 map key",
603+
v: map[int64]interface{}{1: "a"},
604+
expected: `1 = 'a'
605+
`,
606+
},
607+
{
608+
desc: "uint map key",
609+
v: map[uint]interface{}{1: "a"},
610+
expected: `1 = 'a'
611+
`,
612+
},
613+
{
614+
desc: "uint8 map key",
615+
v: map[uint8]interface{}{1: "a"},
616+
expected: `1 = 'a'
617+
`,
618+
},
619+
{
620+
desc: "uint64 map key",
621+
v: map[uint64]interface{}{1: "a"},
622+
expected: `1 = 'a'
623+
`,
624+
},
625+
{
626+
desc: "float32 map key",
627+
v: map[float32]interface{}{
628+
1.1: "a",
629+
1.0020: "b",
630+
},
631+
expected: `'1.002' = 'b'
632+
'1.1' = 'a'
633+
`,
634+
},
635+
{
636+
desc: "float64 map key",
637+
v: map[float64]interface{}{
638+
1.1: "a",
639+
1.0020: "b",
640+
},
641+
expected: `'1.002' = 'b'
642+
'1.1' = 'a'
643+
`,
644+
},
645+
{
646+
desc: "invalid map key",
647+
v: map[struct{ int }]interface{}{{1}: "a"},
592648
err: true,
593649
},
594650
{
595651
desc: "invalid map key but empty",
596-
v: map[int]interface{}{},
652+
v: map[struct{ int }]interface{}{},
597653
expected: "",
598654
},
599655
{
@@ -1565,7 +1621,6 @@ func ExampleMarshal() {
15651621
// configuration file that has commented out sections (example from
15661622
// go-graphite/graphite-clickhouse).
15671623
func ExampleMarshal_commented() {
1568-
15691624
type Common struct {
15701625
Listen string `toml:"listen" comment:"general listener"`
15711626
PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`

unmarshaler.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"math"
99
"reflect"
10+
"strconv"
1011
"strings"
1112
"sync/atomic"
1213
"time"
@@ -1079,6 +1080,33 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value,
10791080
return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err)
10801081
}
10811082
return mk.Elem(), nil
1083+
1084+
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
1085+
key, err := strconv.ParseInt(string(data), 10, 64)
1086+
if err != nil {
1087+
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err)
1088+
}
1089+
return reflect.ValueOf(key).Convert(keyType), nil
1090+
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
1091+
key, err := strconv.ParseUint(string(data), 10, 64)
1092+
if err != nil {
1093+
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err)
1094+
}
1095+
return reflect.ValueOf(key).Convert(keyType), nil
1096+
1097+
case keyType.Kind() == reflect.Float32:
1098+
key, err := strconv.ParseFloat(string(data), 32)
1099+
if err != nil {
1100+
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
1101+
}
1102+
return reflect.ValueOf(float32(key)), nil
1103+
1104+
case keyType.Kind() == reflect.Float64:
1105+
key, err := strconv.ParseFloat(string(data), 64)
1106+
if err != nil {
1107+
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
1108+
}
1109+
return reflect.ValueOf(float64(key)), nil
10821110
}
10831111
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType)
10841112
}

unmarshaler_test.go

Lines changed: 153 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ func TestUnmarshal_Floats(t *testing.T) {
205205
testFn func(t *testing.T, v float64)
206206
err bool
207207
}{
208-
209208
{
210209
desc: "float pi",
211210
input: `3.1415`,
@@ -840,8 +839,10 @@ huey = 'dewey'
840839

841840
return test{
842841
target: &doc{},
843-
expected: &doc{A: []interface{}{"0", "1", "2", "3", "4", "5", "6",
844-
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
842+
expected: &doc{A: []interface{}{
843+
"0", "1", "2", "3", "4", "5", "6",
844+
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17",
845+
}},
845846
}
846847
},
847848
},
@@ -1696,16 +1697,6 @@ B = "data"`,
16961697
}
16971698
},
16981699
},
1699-
{
1700-
desc: "empty map into map with invalid key type",
1701-
input: ``,
1702-
gen: func() test {
1703-
return test{
1704-
target: &map[int]string{},
1705-
expected: &map[int]string{},
1706-
}
1707-
},
1708-
},
17091700
{
17101701
desc: "into map with convertible key type",
17111702
input: `A = "hello"`,
@@ -1942,6 +1933,150 @@ B = "data"`,
19421933
}
19431934
},
19441935
},
1936+
{
1937+
desc: "into map of int to string",
1938+
input: `1 = "a"`,
1939+
gen: func() test {
1940+
return test{
1941+
target: &map[int]string{},
1942+
expected: &map[int]string{1: "a"},
1943+
assert: func(t *testing.T, test test) {
1944+
assert.Equal(t, test.expected, test.target)
1945+
},
1946+
}
1947+
},
1948+
},
1949+
{
1950+
desc: "into map of int8 to string",
1951+
input: `1 = "a"`,
1952+
gen: func() test {
1953+
return test{
1954+
target: &map[int8]string{},
1955+
expected: &map[int8]string{1: "a"},
1956+
assert: func(t *testing.T, test test) {
1957+
assert.Equal(t, test.expected, test.target)
1958+
},
1959+
}
1960+
},
1961+
},
1962+
{
1963+
desc: "into map of int64 to string",
1964+
input: `1 = "a"`,
1965+
gen: func() test {
1966+
return test{
1967+
target: &map[int64]string{},
1968+
expected: &map[int64]string{1: "a"},
1969+
assert: func(t *testing.T, test test) {
1970+
assert.Equal(t, test.expected, test.target)
1971+
},
1972+
}
1973+
},
1974+
},
1975+
{
1976+
desc: "into map of uint to string",
1977+
input: `1 = "a"`,
1978+
gen: func() test {
1979+
return test{
1980+
target: &map[uint]string{},
1981+
expected: &map[uint]string{1: "a"},
1982+
assert: func(t *testing.T, test test) {
1983+
assert.Equal(t, test.expected, test.target)
1984+
},
1985+
}
1986+
},
1987+
},
1988+
{
1989+
desc: "into map of uint8 to string",
1990+
input: `1 = "a"`,
1991+
gen: func() test {
1992+
return test{
1993+
target: &map[uint8]string{},
1994+
expected: &map[uint8]string{1: "a"},
1995+
assert: func(t *testing.T, test test) {
1996+
assert.Equal(t, test.expected, test.target)
1997+
},
1998+
}
1999+
},
2000+
},
2001+
{
2002+
desc: "into map of uint64 to string",
2003+
input: `1 = "a"`,
2004+
gen: func() test {
2005+
return test{
2006+
target: &map[uint64]string{},
2007+
expected: &map[uint64]string{1: "a"},
2008+
assert: func(t *testing.T, test test) {
2009+
assert.Equal(t, test.expected, test.target)
2010+
},
2011+
}
2012+
},
2013+
},
2014+
{
2015+
desc: "into map of uint with invalid key",
2016+
input: `-1 = "a"`,
2017+
gen: func() test {
2018+
return test{
2019+
target: &map[uint]string{},
2020+
err: true,
2021+
}
2022+
},
2023+
},
2024+
{
2025+
desc: "into map of float64 to string",
2026+
input: `'1.01' = "a"`,
2027+
gen: func() test {
2028+
return test{
2029+
target: &map[float64]string{},
2030+
expected: &map[float64]string{1.01: "a"},
2031+
assert: func(t *testing.T, test test) {
2032+
assert.Equal(t, test.expected, test.target)
2033+
},
2034+
}
2035+
},
2036+
},
2037+
{
2038+
desc: "into map of float64 with invalid key",
2039+
input: `key = "a"`,
2040+
gen: func() test {
2041+
return test{
2042+
target: &map[float64]string{},
2043+
err: true,
2044+
}
2045+
},
2046+
},
2047+
{
2048+
desc: "into map of float32 to string",
2049+
input: `'1.01' = "a"`,
2050+
gen: func() test {
2051+
return test{
2052+
target: &map[float32]string{},
2053+
expected: &map[float32]string{1.01: "a"},
2054+
assert: func(t *testing.T, test test) {
2055+
assert.Equal(t, test.expected, test.target)
2056+
},
2057+
}
2058+
},
2059+
},
2060+
{
2061+
desc: "into map of float32 with invalid key",
2062+
input: `key = "a"`,
2063+
gen: func() test {
2064+
return test{
2065+
target: &map[float32]string{},
2066+
err: true,
2067+
}
2068+
},
2069+
},
2070+
{
2071+
desc: "invalid map key type",
2072+
input: `1 = "a"`,
2073+
gen: func() test {
2074+
return test{
2075+
target: &map[struct{ int }]string{},
2076+
err: true,
2077+
}
2078+
},
2079+
},
19452080
}
19462081

19472082
for _, e := range examples {
@@ -2653,7 +2788,7 @@ func TestIssue772(t *testing.T) {
26532788
FileHandling `toml:"filehandling"`
26542789
}
26552790

2656-
var defaultConfigFile = []byte(`
2791+
defaultConfigFile := []byte(`
26572792
[filehandling]
26582793
pattern = "reach-masterdev-"`)
26592794

@@ -2750,7 +2885,7 @@ func TestIssue866(t *testing.T) {
27502885
PipelineMapping map[string]*Pipeline `toml:"pipelines"`
27512886
}
27522887

2753-
var badToml = `
2888+
badToml := `
27542889
[pipelines.register]
27552890
mapping.inst.req = [
27562891
["param1", "value1"],
@@ -2768,7 +2903,7 @@ mapping.inst.res = [
27682903
t.Fatal("unmarshal failed with mismatch value")
27692904
}
27702905

2771-
var goodTooToml = `
2906+
goodTooToml := `
27722907
[pipelines.register]
27732908
mapping.inst.req = [
27742909
["param1", "value1"],
@@ -2783,7 +2918,7 @@ mapping.inst.req = [
27832918
t.Fatal("unmarshal failed with mismatch value")
27842919
}
27852920

2786-
var goodToml = `
2921+
goodToml := `
27872922
[pipelines.register.mapping.inst]
27882923
req = [
27892924
["param1", "value1"],
@@ -3362,7 +3497,7 @@ func TestOmitEmpty(t *testing.T) {
33623497
X []elem `toml:",inline"`
33633498
}
33643499

3365-
d := doc{X: []elem{elem{
3500+
d := doc{X: []elem{{
33663501
Foo: "test",
33673502
Inner: inner{
33683503
V: "alue",
@@ -3785,7 +3920,6 @@ func (k *CustomUnmarshalerKey) UnmarshalTOML(value *unstable.Node) error {
37853920
}
37863921
k.A = item
37873922
return nil
3788-
37893923
}
37903924

37913925
func TestUnmarshal_CustomUnmarshaler(t *testing.T) {

0 commit comments

Comments
 (0)