Просмотр исходного кода

Ordered map support with MapSlice.

Gustavo Niemeyer 11 лет назад
Родитель
Сommit
3e542fbf7c
5 измененных файлов с 112 добавлено и 25 удалено
  1. 67 16
      decode.go
  2. 15 8
      decode_test.go
  3. 15 1
      encode.go
  4. 6 0
      encode_test.go
  5. 9 0
      yaml.go

+ 67 - 16
decode.go

@@ -30,9 +30,9 @@ type node struct {
 // Parser, produces a node tree out of a libyaml event stream.
 
 type parser struct {
-	parser yaml_parser_t
-	event  yaml_event_t
-	doc    *node
+	parser  yaml_parser_t
+	event   yaml_event_t
+	doc     *node
 }
 
 func newParser(b []byte) *parser {
@@ -187,10 +187,13 @@ func (p *parser) mapping() *node {
 type decoder struct {
 	doc     *node
 	aliases map[string]bool
+	mapType reflect.Type
 }
 
+var defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
+
 func newDecoder() *decoder {
-	d := &decoder{}
+	d := &decoder{mapType: defaultMapType}
 	d.aliases = make(map[string]bool)
 	return d
 }
@@ -457,24 +460,36 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 	if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
 		defer set()
 	}
-	if out.Kind() == reflect.Struct {
+	switch out.Kind() {
+	case reflect.Struct:
 		return d.mappingStruct(n, out)
-	}
-
-	if out.Kind() == reflect.Interface {
-		// No type hints. Will have to use a generic map.
-		iface := out
-		out = settableValueOf(make(map[interface{}]interface{}))
-		iface.Set(out)
-	}
-
-	if out.Kind() != reflect.Map {
+	case reflect.Slice:
+		return d.mappingSlice(n, out)
+	case reflect.Map:
+		// okay
+	case reflect.Interface:
+		if d.mapType.Kind() == reflect.Map {
+			iface := out
+			out = reflect.MakeMap(d.mapType)
+			iface.Set(out)
+		} else {
+			slicev := reflect.New(d.mapType).Elem()
+			if !d.mappingSlice(n, slicev) {
+				return false
+			}
+			out.Set(slicev)
+			return true
+		}
+	default:
 		return false
 	}
 	outt := out.Type()
 	kt := outt.Key()
 	et := outt.Elem()
 
+	mapType := d.mapType
+	d.mapType = outt
+
 	if out.IsNil() {
 		out.Set(reflect.MakeMap(outt))
 	}
@@ -499,6 +514,42 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
 			}
 		}
 	}
+	d.mapType = mapType
+	return true
+}
+
+var mapItemType = reflect.TypeOf(MapItem{})
+
+func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
+	outt := out.Type()
+	if outt.Elem() != mapItemType {
+		return false
+	}
+	if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
+		defer set()
+	}
+
+	mapType := d.mapType
+	d.mapType = outt
+
+	var slice []MapItem
+	var l = len(n.children)
+	for i := 0; i < l; i += 2 {
+		if isMerge(n.children[i]) {
+			d.merge(n.children[i+1], out)
+			continue
+		}
+		item := MapItem{}
+		k := reflect.ValueOf(&item.Key).Elem()
+		if d.unmarshal(n.children[i], k) {
+			v := reflect.ValueOf(&item.Value).Elem()
+			if d.unmarshal(n.children[i+1], v) {
+				slice = append(slice, item)
+			}
+		}
+	}
+	out.Set(reflect.ValueOf(slice))
+	d.mapType = mapType
 	return true
 }
 
@@ -544,7 +595,7 @@ func (d *decoder) merge(n *node, out reflect.Value) {
 		d.unmarshal(n, out)
 	case sequenceNode:
 		// Step backwards as earlier nodes take precedence.
-		for i := len(n.children)-1; i >= 0; i-- {
+		for i := len(n.children) - 1; i >= 0; i-- {
 			ni := n.children[i]
 			if ni.kind == aliasNode {
 				an, ok := d.doc.anchors[ni.value]

+ 15 - 8
decode_test.go

@@ -200,7 +200,10 @@ var unmarshalTests = []struct {
 	// Map inside interface with no type hints.
 	{
 		"a: {b: c}",
-		map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
+		map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
+	}, {
+		"a: {b: c}",
+		map[string]interface{}{"a": map[string]interface{}{"b": "c"}},
 	},
 
 	// Structs and type conversions.
@@ -399,6 +402,12 @@ var unmarshalTests = []struct {
 		"a: !!binary |\n  " + strings.Repeat("A", 70) + "\n  ==\n",
 		map[string]string{"a": strings.Repeat("\x00", 52)},
 	},
+
+	// Ordered maps.
+	{
+		"{b: 2, a: 1, d: 4, c: 3, sub: {e: 5}}",
+		&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
+	},
 }
 
 type inlineB struct {
@@ -418,13 +427,11 @@ func (s *S) TestUnmarshal(c *C) {
 		case reflect.Map:
 			value = reflect.MakeMap(t).Interface()
 		case reflect.String:
-			t := reflect.ValueOf(item.value).Type()
-			v := reflect.New(t)
-			value = v.Interface()
+			value = reflect.New(t).Interface()
+		case reflect.Ptr:
+			value = reflect.New(t.Elem()).Interface()
 		default:
-			pt := reflect.ValueOf(item.value).Type()
-			pv := reflect.New(pt.Elem())
-			value = pv.Interface()
+			c.Fatalf("missing case for %s", t)
 		}
 		err := yaml.Unmarshal([]byte(item.data), value)
 		c.Assert(err, IsNil, Commentf("Item #%d", i))
@@ -613,7 +620,7 @@ inlineSequenceMap:
 `
 
 func (s *S) TestMerge(c *C) {
-	var want = map[interface{}]interface{}{
+	var want = map[string]interface{}{
 		"x":     1,
 		"y":     2,
 		"r":     10,

+ 15 - 1
encode.go

@@ -85,7 +85,11 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
 	case reflect.Struct:
 		e.structv(tag, in)
 	case reflect.Slice:
-		e.slicev(tag, in)
+		if in.Type().Elem() == mapItemType {
+			e.itemsv(tag, in)
+		} else {
+			e.slicev(tag, in)
+		}
 	case reflect.String:
 		e.stringv(tag, in)
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -116,6 +120,16 @@ func (e *encoder) mapv(tag string, in reflect.Value) {
 	})
 }
 
+func (e *encoder) itemsv(tag string, in reflect.Value) {
+	e.mappingv(tag, func() {
+		slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
+		for _, item := range slice {
+			e.marshal("", reflect.ValueOf(item.Key))
+			e.marshal("", reflect.ValueOf(item.Value))
+		}
+	})
+}
+
 func (e *encoder) structv(tag string, in reflect.Value) {
 	sinfo, err := getStructInfo(in.Type())
 	if err != nil {

+ 6 - 0
encode_test.go

@@ -248,6 +248,12 @@ var marshalTests = []struct {
 		"a: !!binary gIGC\n",
 	},
 
+	// Ordered maps.
+	{
+		&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
+		"b: 2\na: 1\nd: 4\nc: 3\nsub:\n  e: 5\n",
+	},
+
 	// Escaping of tags.
 	{
 		map[string]interface{}{"a": typeWithGetter{"foo!bar", 1}},

+ 9 - 0
yaml.go

@@ -30,6 +30,15 @@ func handleErr(err *error) {
 	}
 }
 
+// MapSlice encodes and decodes as a YAML map.
+// The order of keys is preserved when encoding and decoding.
+type MapSlice []MapItem
+
+// MapItem is an item in a MapSlice.
+type MapItem struct {
+	Key, Value interface{}
+}
+
 // The Setter interface may be implemented by types to do their own custom
 // unmarshalling of YAML values, rather than being implicitly assigned by
 // the yaml package machinery. If setting the value works, the method should