Kaynağa Gözat

Handle inlined structs referenced as a pointer.

Gustavo Niemeyer 7 yıl önce
ebeveyn
işleme
b53452edad
5 değiştirilmiş dosya ile 100 ekleme ve 8 silme
  1. 22 2
      decode.go
  2. 26 0
      decode_test.go
  3. 21 1
      encode.go
  4. 20 0
      encode_test.go
  5. 11 5
      yaml.go

+ 22 - 2
decode.go

@@ -455,6 +455,26 @@ func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unm
 	return out, false, false
 }
 
+func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) {
+	if n.ShortTag() == nullTag {
+		return reflect.Value{}
+	}
+	for _, num := range index {
+		for {
+			if v.Kind() == reflect.Ptr {
+				if v.IsNil() {
+					v.Set(reflect.New(v.Type().Elem()))
+				}
+				v = v.Elem()
+				continue
+			}
+			break
+		}
+		v = v.Field(num)
+	}
+	return v
+}
+
 func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
 	if out.Type() == nodeType {
 		out.Set(reflect.ValueOf(n).Elem())
@@ -835,7 +855,7 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
 	}
 
 	for _, index := range sinfo.InlineUnmarshalers {
-		field := out.FieldByIndex(index)
+		field := d.fieldByIndex(n, out, index)
 		d.prepare(n, field)
 	}
 
@@ -866,7 +886,7 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
 			if info.Inline == nil {
 				field = out.Field(info.Num)
 			} else {
-				field = out.FieldByIndex(info.Inline)
+				field = d.fieldByIndex(n, out, info.Inline)
 			}
 			d.unmarshal(n.Children[i+1], field)
 		} else if sinfo.InlineMap != -1 {

+ 26 - 0
decode_test.go

@@ -525,6 +525,27 @@ var unmarshalTests = []struct {
 		}{1, inlineB{2, inlineC{3}}},
 	},
 
+	// Struct inlining as a pointer.
+	{
+		"a: 1\nb: 2\nc: 3\n",
+		&struct {
+			A int
+			C *inlineB `yaml:",inline"`
+		}{1, &inlineB{2, inlineC{3}}},
+	}, {
+		"a: 1\n",
+		&struct {
+			A int
+			C *inlineB `yaml:",inline"`
+		}{1, nil},
+	}, {
+		"a: 1\nc: 3\nd: 4\n",
+		&struct {
+			A int
+			C *inlineD `yaml:",inline"`
+		}{1, &inlineD{&inlineC{3}, 4}},
+	},
+
 	// Map inlining
 	{
 		"a: 1\nb: 2\nc: 3\n",
@@ -745,6 +766,11 @@ type inlineC struct {
 	C int
 }
 
+type inlineD struct {
+	C *inlineC `yaml:",inline"`
+	D int
+}
+
 func (s *S) TestUnmarshal(c *C) {
 	for i, item := range unmarshalTests {
 		c.Logf("test %d: %q", i, item.data)

+ 21 - 1
encode.go

@@ -170,6 +170,23 @@ func (e *encoder) mapv(tag string, in reflect.Value) {
 	})
 }
 
+func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
+	for _, num := range index {
+		for {
+			if v.Kind() == reflect.Ptr {
+				if v.IsNil() {
+					return reflect.Value{}
+				}
+				v = v.Elem()
+				continue
+			}
+			break
+		}
+		v = v.Field(num)
+	}
+	return v
+}
+
 func (e *encoder) structv(tag string, in reflect.Value) {
 	sinfo, err := getStructInfo(in.Type())
 	if err != nil {
@@ -181,7 +198,10 @@ func (e *encoder) structv(tag string, in reflect.Value) {
 			if info.Inline == nil {
 				value = in.Field(info.Num)
 			} else {
-				value = in.FieldByIndex(info.Inline)
+				value = e.fieldByIndex(in, info.Inline)
+				if !value.IsValid() {
+					continue
+				}
 			}
 			if info.OmitEmpty && isZero(value) {
 				continue

+ 20 - 0
encode_test.go

@@ -274,6 +274,26 @@ var marshalTests = []struct {
 		}{1, inlineB{2, inlineC{3}}},
 		"a: 1\nb: 2\nc: 3\n",
 	},
+	// Struct inlining as a pointer
+	{
+		&struct {
+			A int
+			C *inlineB `yaml:",inline"`
+		}{1, &inlineB{2, inlineC{3}}},
+		"a: 1\nb: 2\nc: 3\n",
+	}, {
+		&struct {
+			A int
+			C *inlineB `yaml:",inline"`
+		}{1, nil},
+		"a: 1\n",
+	}, {
+		&struct {
+			A int
+			D *inlineD `yaml:",inline"`
+		}{1, &inlineD{&inlineC{3}, 4}},
+		"a: 1\nc: 3\nd: 4\n",
+	},
 
 	// Map inlining
 	{

+ 11 - 5
yaml.go

@@ -381,11 +381,18 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
 					return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
 				}
 				inlineMap = info.Num
-			case reflect.Struct:
-				if reflect.PtrTo(field.Type).Implements(unmarshalerType) {
+			case reflect.Struct, reflect.Ptr:
+				ftype := field.Type
+				for ftype.Kind() == reflect.Ptr {
+					ftype = ftype.Elem()
+				}
+				if ftype.Kind() != reflect.Struct {
+					return nil, errors.New("option ,inline may only be used on a struct or map field")
+				}
+				if reflect.PtrTo(ftype).Implements(unmarshalerType) {
 					inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
 				} else {
-					sinfo, err := getStructInfo(field.Type)
+					sinfo, err := getStructInfo(ftype)
 					if err != nil {
 						return nil, err
 					}
@@ -408,8 +415,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
 					}
 				}
 			default:
-				//return nil, errors.New("option ,inline needs a struct value or map field")
-				return nil, errors.New("option ,inline needs a struct value field")
+				return nil, errors.New("option ,inline may only be used on a struct or map field")
 			}
 			continue
 		}