Support map merging.
Fixes #1.
diff --git a/decode.go b/decode.go
index 75a2c42..ab2b8f7 100644
--- a/decode.go
+++ b/decode.go
@@ -438,6 +438,10 @@
}
l := len(n.children)
for i := 0; i < l; i += 2 {
+ if isMerge(n.children[i]) {
+ d.merge(n.children[i+1], out)
+ continue
+ }
k := reflect.New(kt).Elem()
if d.unmarshal(n.children[i], k) {
e := reflect.New(et).Elem()
@@ -457,7 +461,12 @@
name := settableValueOf("")
l := len(n.children)
for i := 0; i < l; i += 2 {
- if !d.unmarshal(n.children[i], name) {
+ ni := n.children[i]
+ if isMerge(ni) {
+ d.merge(n.children[i+1], out)
+ continue
+ }
+ if !d.unmarshal(ni, name) {
continue
}
if info, ok := sinfo.FieldsMap[name.String()]; ok {
@@ -472,3 +481,37 @@
}
return true
}
+
+func (d *decoder) merge(n *node, out reflect.Value) {
+ const wantMap = "map merge requires map or sequence of maps as the value"
+ switch n.kind {
+ case mappingNode:
+ d.unmarshal(n, out)
+ case aliasNode:
+ an, ok := d.doc.anchors[n.value]
+ if ok && an.kind != mappingNode {
+ panic(wantMap)
+ }
+ d.unmarshal(n, out)
+ case sequenceNode:
+ // Step backwards as earlier nodes take precedence.
+ for i := len(n.children)-1; i >= 0; i-- {
+ ni := n.children[i]
+ if ni.kind == aliasNode {
+ an, ok := d.doc.anchors[ni.value]
+ if ok && an.kind != mappingNode {
+ panic(wantMap)
+ }
+ } else if ni.kind != mappingNode {
+ panic(wantMap)
+ }
+ d.unmarshal(ni, out)
+ }
+ default:
+ panic(wantMap)
+ }
+}
+
+func isMerge(n *node) bool {
+ return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == "!!merge" || n.tag == "tag:yaml.org,2002:merge")
+}
diff --git a/decode_test.go b/decode_test.go
index 55db804..5702909 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -505,6 +505,98 @@
c.Assert(m["ghi"].value, Equals, 3)
}
+// From http://yaml.org/type/merge.html
+var mergeTests = `
+anchors:
+ - &CENTER { "x": 1, "y": 2 }
+ - &LEFT { "x": 0, "y": 2 }
+ - &BIG { "r": 10 }
+ - &SMALL { "r": 1 }
+
+# All the following maps are equal:
+
+plain:
+ # Explicit keys
+ "x": 1
+ "y": 2
+ "r": 10
+ label: center/big
+
+mergeOne:
+ # Merge one map
+ << : *CENTER
+ "r": 10
+ label: center/big
+
+mergeMultiple:
+ # Merge multiple maps
+ << : [ *CENTER, *BIG ]
+ label: center/big
+
+override:
+ # Override
+ << : [ *BIG, *LEFT, *SMALL ]
+ "x": 1
+ label: center/big
+
+shortTag:
+ # Explicit short merge tag
+ !!merge "<<" : [ *CENTER, *BIG ]
+ label: center/big
+
+longTag:
+ # Explicit merge long tag
+ !<tag:yaml.org,2002:merge> "<<" : [ *CENTER, *BIG ]
+ label: center/big
+
+inlineMap:
+ # Inlined map
+ << : {"x": 1, "y": 2, "r": 10}
+ label: center/big
+
+inlineSequenceMap:
+ # Inlined map in sequence
+ << : [ *CENTER, {"r": 10} ]
+ label: center/big
+`
+
+func (s *S) TestMerge(c *C) {
+ var want = map[interface{}]interface{}{
+ "x": 1,
+ "y": 2,
+ "r": 10,
+ "label": "center/big",
+ }
+
+ var m map[string]interface{}
+ err := yaml.Unmarshal([]byte(mergeTests), &m)
+ c.Assert(err, IsNil)
+ for name, test := range m {
+ if name == "anchors" {
+ continue
+ }
+ c.Assert(test, DeepEquals, want, Commentf("test %q failed", name))
+ }
+}
+
+func (s *S) TestMergeStruct(c *C) {
+ type Data struct {
+ X, Y, R int
+ Label string
+ }
+ want := Data{1, 2, 10, "center/big"}
+
+ var m map[string]Data
+ err := yaml.Unmarshal([]byte(mergeTests), &m)
+ c.Assert(err, IsNil)
+ for name, test := range m {
+ if name == "anchors" {
+ continue
+ }
+ c.Assert(test, Equals, want, Commentf("test %q failed", name))
+ }
+}
+
//var data []byte
//func init() {
// var err error
diff --git a/resolve.go b/resolve.go
index dbda017..fdc4909 100644
--- a/resolve.go
+++ b/resolve.go
@@ -45,6 +45,7 @@
{math.Inf(+1), "!!float", []string{".inf", ".Inf", ".INF"}},
{math.Inf(+1), "!!float", []string{"+.inf", "+.Inf", "+.INF"}},
{math.Inf(-1), "!!float", []string{"-.inf", "-.Inf", "-.INF"}},
+ {"<<", "!!merge", []string{"<<"}},
}
m := resolveMap
@@ -139,9 +140,6 @@
}
// XXX Handle timestamps here.
- case '<':
- // XXX Handle merge (<<) here.
-
default:
panic("resolveTable item not yet handled: " +
string([]byte{c}) + " (with " + in + ")")