Browse Source

Maps with string keys are now map[string] by default.

Until now, decoding a map into an interface{} would produce a generic
map[interface{}]interface{}. From now on, as long as all of the map keys
are strings, they will automatically decode as map[string]interface{}.
Gustavo Niemeyer 7 years ago
parent
commit
14f799f057
2 changed files with 55 additions and 17 deletions
  1. 42 9
      decode.go
  2. 13 8
      decode_test.go

+ 42 - 9
decode.go

@@ -51,6 +51,8 @@ func (n *Node) implicit() bool {
 	return n.Style&(SingleQuotedStyle|DoubleQuotedStyle) == 0 && (n.Tag == "" || n.Tag == "!")
 }
 
+// TODO Quite some garbage being generated by these common functions.
+
 func (n *Node) ShortTag() string {
 	tag := n.LongTag()
 	if strings.HasPrefix(tag, longTagPrefix) {
@@ -331,9 +333,11 @@ func (p *parser) mapping() *Node {
 type decoder struct {
 	doc     *Node
 	aliases map[*Node]bool
-	mapType reflect.Type
 	terrors []string
 
+	stringMapType  reflect.Type
+	generalMapType reflect.Type
+
 	knownFields bool
 	uniqueKeys  bool
 }
@@ -341,14 +345,19 @@ type decoder struct {
 var (
 	nodeType       = reflect.TypeOf(Node{})
 	durationType   = reflect.TypeOf(time.Duration(0))
-	defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
-	ifaceType      = defaultMapType.Elem()
+	stringMapType  = reflect.TypeOf(map[string]interface{}{})
+	generalMapType = reflect.TypeOf(map[interface{}]interface{}{})
+	ifaceType      = generalMapType.Elem()
 	timeType       = reflect.TypeOf(time.Time{})
 	ptrTimeType    = reflect.TypeOf(&time.Time{})
 )
 
 func newDecoder() *decoder {
-	d := &decoder{mapType: defaultMapType, uniqueKeys: true}
+	d := &decoder{
+		stringMapType: stringMapType,
+		generalMapType: generalMapType,
+		uniqueKeys: true,
+	}
 	d.aliases = make(map[*Node]bool)
 	return d
 }
@@ -721,19 +730,29 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
 		// okay
 	case reflect.Interface:
 		iface := out
-		out = reflect.MakeMap(d.mapType)
+		if isStringMap(n) {
+			out = reflect.MakeMap(d.stringMapType)
+		} else {
+			out = reflect.MakeMap(d.generalMapType)
+		}
 		iface.Set(out)
 	default:
 		d.terror(n, yaml_MAP_TAG, out)
 		return false
 	}
+
 	outt := out.Type()
 	kt := outt.Key()
 	et := outt.Elem()
 
-	mapType := d.mapType
-	if outt.Key() == ifaceType && outt.Elem() == ifaceType {
-		d.mapType = outt
+	stringMapType := d.stringMapType
+	generalMapType := d.generalMapType
+	if outt.Elem() == ifaceType {
+		if outt.Key().Kind() == reflect.String {
+			d.stringMapType = outt
+		} else if outt.Key() == ifaceType {
+			d.generalMapType = outt
+		}
 	}
 
 	if out.IsNil() {
@@ -759,7 +778,21 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
 			}
 		}
 	}
-	d.mapType = mapType
+	d.stringMapType = stringMapType
+	d.generalMapType = generalMapType
+	return true
+}
+
+func isStringMap(n *Node) bool {
+	if n.Kind != MappingNode {
+		return false
+	}
+	l := len(n.Children)
+	for i := 0; i < l; i++ {
+		if n.Children[i].LongTag() != yaml_STR_TAG {
+			return false
+		}
+	}
 	return true
 }
 

+ 13 - 8
decode_test.go

@@ -196,7 +196,12 @@ var unmarshalTests = []struct {
 	// Map inside interface with no type hints.
 	{
 		"a: {b: c}",
-		map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
+		map[interface{}]interface{}{"a": map[string]interface{}{"b": "c"}},
+	},
+	// Non-string map inside interface with no type hints.
+	{
+		"a: {b: c, 1: d}",
+		map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c", 1: "d"}},
 	},
 
 	// Structs and type conversions.
@@ -513,7 +518,7 @@ var unmarshalTests = []struct {
 	// issue #295 (allow scalars with colons in flow mappings and sequences)
 	{
 		"a: {b: https://github.com/go-yaml/yaml}",
-		map[string]interface{}{"a": map[interface{}]interface{}{
+		map[string]interface{}{"a": map[string]interface{}{
 			"b": "https://github.com/go-yaml/yaml",
 		}},
 	},
@@ -555,7 +560,7 @@ var unmarshalTests = []struct {
 	// Issue #39.
 	{
 		"a:\n b:\n  c: d\n",
-		map[string]struct{ B interface{} }{"a": {map[interface{}]interface{}{"c": "d"}}},
+		map[string]struct{ B interface{} }{"a": {map[string]interface{}{"c": "d"}}},
 	},
 
 	// Custom map type.
@@ -682,7 +687,7 @@ var unmarshalTests = []struct {
 	// yaml-test-suite 3GZX: Spec Example 7.1. Alias Nodes
 	{
 		"First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor\n",
-		map[interface{}]interface{}{
+		map[string]interface{}{
 			"First occurrence":  "Foo",
 			"Second occurrence": "Foo",
 			"Override anchor":   "Bar",
@@ -696,7 +701,7 @@ var unmarshalTests = []struct {
 	},
 }
 
-type M map[interface{}]interface{}
+type M map[string]interface{}
 
 type inlineB struct {
 	B       int
@@ -759,12 +764,12 @@ var decoderTests = []struct {
 }, {
 	"a: b",
 	[]interface{}{
-		map[interface{}]interface{}{"a": "b"},
+		map[string]interface{}{"a": "b"},
 	},
 }, {
 	"---\na: b\n...\n",
 	[]interface{}{
-		map[interface{}]interface{}{"a": "b"},
+		map[string]interface{}{"a": "b"},
 	},
 }, {
 	"---\n'hello'\n...\n---\ngoodbye\n...\n",
@@ -855,7 +860,7 @@ var unmarshalerTests = []struct {
 	data, tag string
 	value     interface{}
 }{
-	{"_: {hi: there}", "!!map", map[interface{}]interface{}{"hi": "there"}},
+	{"_: {hi: there}", "!!map", map[string]interface{}{"hi": "there"}},
 	{"_: [1,A]", "!!seq", []interface{}{1, "A"}},
 	{"_: 10", "!!int", 10},
 	{"_: null", "!!null", nil},