Procházet zdrojové kódy

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 před 7 roky
rodič
revize
389beb8b70
2 změnil soubory, kde provedl 42 přidání a 30 odebrání
  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}}