Skip to content

Commit

Permalink
Maps with string keys are now map[string] by default.
Browse files Browse the repository at this point in the history
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{}.
  • Loading branch information
niemeyer committed Mar 22, 2019
1 parent 309c9d1 commit 14f799f
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 17 deletions.
51 changes: 42 additions & 9 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -331,24 +333,31 @@ 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
}

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
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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
}

Expand Down
21 changes: 13 additions & 8 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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",
}},
},
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand All @@ -696,7 +701,7 @@ var unmarshalTests = []struct {
},
}

type M map[interface{}]interface{}
type M map[string]interface{}

type inlineB struct {
B int
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit 14f799f

Please sign in to comment.