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

Preserve data in existing fields that are not decoded.

This behavior change means it's now possible to pre-initialize
values and then provide them to decoding, with the result being
a sum of the previous data and the successfuly decoded fields.

Particular care needs to be taken in loops that decode into the same
value repeatedly. If the variable decoded into is declared in a scope
out of the loop block, following iterations will observe data decoded in
previous iterations. To fix that just move the variable declaration
inside the loop.

Discussed in #395.
Gustavo Niemeyer 7 лет назад
Родитель
Сommit
389beb8b70
2 измененных файлов с 42 добавлено и 30 удалено
  1. 11 21
      decode.go
  2. 31 9
      decode_test.go

+ 11 - 21
decode.go

@@ -483,12 +483,14 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
 		}
 	}
 	if resolved == nil {
-		if out.Kind() == reflect.Map && !out.CanAddr() {
-			resetMap(out)
-		} else {
-			out.Set(reflect.Zero(out.Type()))
+		if out.CanAddr() {
+			switch out.Kind() {
+			case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
+				out.Set(reflect.Zero(out.Type()))
+				return true
+			}
 		}
-		return true
+		return false
 	}
 	if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
 		// We've resolved to exactly the type we want, so use that.
@@ -522,16 +524,10 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
 			out.SetString(resolved.(string))
 			return true
 		}
-		if resolved != nil {
-			out.SetString(n.Value)
-			return true
-		}
+		out.SetString(n.Value)
+		return true
 	case reflect.Interface:
-		if resolved == nil {
-			out.Set(reflect.Zero(out.Type()))
-		} else {
-			out.Set(reflect.ValueOf(resolved))
-		}
+		out.Set(reflect.ValueOf(resolved))
 		return true
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		// This used to work in v2, but it's very unfriendly.
@@ -628,13 +624,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
 			return true
 		}
 	case reflect.Ptr:
-		if out.Type().Elem() == reflect.TypeOf(resolved) {
-			// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
-			elem := reflect.New(out.Type().Elem())
-			elem.Elem().Set(reflect.ValueOf(resolved))
-			out.Set(elem)
-			return true
-		}
+		panic("yaml internal error: please report the issue")
 	}
 	d.terror(n, tag, out)
 	return false

+ 31 - 9
decode_test.go

@@ -489,7 +489,7 @@ var unmarshalTests = []struct {
 		map[string]*string{"foo": nil},
 	}, {
 		"foo: null",
-		map[string]string{"foo": ""},
+		map[string]string{},
 	}, {
 		"foo: null",
 		map[string]interface{}{"foo": nil},
@@ -501,7 +501,7 @@ var unmarshalTests = []struct {
 		map[string]*string{"foo": nil},
 	}, {
 		"foo: ~",
-		map[string]string{"foo": ""},
+		map[string]string{},
 	}, {
 		"foo: ~",
 		map[string]interface{}{"foo": nil},
@@ -1279,18 +1279,40 @@ var unmarshalNullTests = []func() interface{}{
 
 func (s *S) TestUnmarshalNull(c *C) {
 	for _, test := range unmarshalNullTests {
-		item := test()
-		zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface()
-		err := yaml.Unmarshal([]byte("null"), item)
+		pristine := test()
+		decoded := test()
+		zero := reflect.Zero(reflect.TypeOf(decoded).Elem()).Interface()
+		err := yaml.Unmarshal([]byte("null"), decoded)
 		c.Assert(err, IsNil)
-		if reflect.TypeOf(item).Kind() == reflect.Map {
-			c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface())
-		} else {
-			c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero)
+		switch pristine.(type) {
+		case *interface{}, **string, **int, *map[string]int:
+			c.Assert(reflect.ValueOf(decoded).Elem().Interface(), DeepEquals, zero)
+		default:
+			c.Assert(reflect.ValueOf(decoded).Interface(), DeepEquals, pristine)
 		}
 	}
 }
 
+func (s *S) TestUnmarshalPreservesData(c *C) {
+	var v struct {
+		A, B int
+		C int `yaml:"-"`
+	}
+	v.A = 42
+	v.C = 88
+	err := yaml.Unmarshal([]byte("---"), &v)
+	c.Assert(err, IsNil)
+	c.Assert(v.A, Equals, 42)
+	c.Assert(v.B, Equals, 0)
+	c.Assert(v.C, Equals, 88)
+
+	err = yaml.Unmarshal([]byte("b: 21\nc: 99"), &v)
+	c.Assert(err, IsNil)
+	c.Assert(v.A, Equals, 42)
+	c.Assert(v.B, Equals, 21)
+	c.Assert(v.C, Equals, 88)
+}
+
 func (s *S) TestUnmarshalSliceOnPreset(c *C) {
 	// Issue #48.
 	v := struct{ A []int }{[]int{1}}