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

Handle inlined fields implementing Unmarshaler.

Closes #125.
Gustavo Niemeyer 7 лет назад
Родитель
Сommit
c07216459f
3 измененных файлов с 64 добавлено и 17 удалено
  1. 7 2
      decode.go
  2. 23 0
      decode_test.go
  3. 34 15
      yaml.go

+ 7 - 2
decode.go

@@ -825,8 +825,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
 	if err != nil {
 		panic(err)
 	}
-	name := settableValueOf("")
-	l := len(n.Children)
 
 	var inlineMap reflect.Value
 	var elemType reflect.Type
@@ -836,10 +834,17 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
 		elemType = inlineMap.Type().Elem()
 	}
 
+	for _, index := range sinfo.InlineUnmarshalers {
+		field := out.FieldByIndex(index)
+		d.prepare(n, field)
+	}
+
 	var doneFields []bool
 	if d.uniqueKeys {
 		doneFields = make([]bool, len(sinfo.FieldsList))
 	}
+	name := settableValueOf("")
+	l := len(n.Children)
 	for i := 0; i < l; i += 2 {
 		ni := n.Children[i]
 		if isMerge(ni) {

+ 23 - 0
decode_test.go

@@ -929,6 +929,15 @@ type unmarshalerValue struct {
 	Field unmarshalerType "_"
 }
 
+type unmarshalerInlined struct {
+	Field   *unmarshalerType "_"
+	Inlined unmarshalerType  `yaml:",inline"`
+}
+
+type unmarshalerInlinedTwice struct {
+	InlinedTwice unmarshalerInlined `yaml:",inline"`
+}
+
 type obsoleteUnmarshalerType struct {
 	value interface{}
 }
@@ -988,6 +997,20 @@ func (s *S) TestUnmarshalerValueField(c *C) {
 	}
 }
 
+func (s *S) TestUnmarshalerInlinedField(c *C) {
+	obj := &unmarshalerInlined{}
+	err := yaml.Unmarshal([]byte("_: a\ninlined: b\n"), obj)
+	c.Assert(err, IsNil)
+	c.Assert(obj.Field, DeepEquals, &unmarshalerType{"a"})
+	c.Assert(obj.Inlined, DeepEquals, unmarshalerType{map[string]interface{}{"_": "a", "inlined": "b"}})
+
+	twc := &unmarshalerInlinedTwice{}
+	err = yaml.Unmarshal([]byte("_: a\ninlined: b\n"), twc)
+	c.Assert(err, IsNil)
+	c.Assert(twc.InlinedTwice.Field, DeepEquals, &unmarshalerType{"a"})
+	c.Assert(twc.InlinedTwice.Inlined, DeepEquals, unmarshalerType{map[string]interface{}{"_": "a", "inlined": "b"}})
+}
+
 func (s *S) TestUnmarshalerWholeDocument(c *C) {
 	obj := &obsoleteUnmarshalerType{}
 	err := yaml.Unmarshal([]byte(unmarshalerTests[0].data), obj)

+ 34 - 15
yaml.go

@@ -296,6 +296,10 @@ type structInfo struct {
 	// InlineMap is the number of the field in the struct that
 	// contains an ,inline map, or -1 if there's none.
 	InlineMap int
+
+	// InlineUnmarshalers holds indexes to inlined fields that
+	// contain unmarshaler values.
+	InlineUnmarshalers [][]int
 }
 
 type fieldInfo struct {
@@ -313,6 +317,12 @@ type fieldInfo struct {
 
 var structMap = make(map[reflect.Type]*structInfo)
 var fieldMapMutex sync.RWMutex
+var unmarshalerType reflect.Type
+
+func init() {
+	var v Unmarshaler
+	unmarshalerType = reflect.ValueOf(&v).Elem().Type()
+}
 
 func getStructInfo(st reflect.Type) (*structInfo, error) {
 	fieldMapMutex.RLock()
@@ -326,6 +336,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
 	fieldsMap := make(map[string]fieldInfo)
 	fieldsList := make([]fieldInfo, 0, n)
 	inlineMap := -1
+	inlineUnmarshalers := [][]int(nil)
 	for i := 0; i != n; i++ {
 		field := st.Field(i)
 		if field.PkgPath != "" && !field.Anonymous {
@@ -371,23 +382,30 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
 				}
 				inlineMap = info.Num
 			case reflect.Struct:
-				sinfo, err := getStructInfo(field.Type)
-				if err != nil {
-					return nil, err
-				}
-				for _, finfo := range sinfo.FieldsList {
-					if _, found := fieldsMap[finfo.Key]; found {
-						msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
-						return nil, errors.New(msg)
+				if reflect.PtrTo(field.Type).Implements(unmarshalerType) {
+					inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
+				} else {
+					sinfo, err := getStructInfo(field.Type)
+					if err != nil {
+						return nil, err
+					}
+					for _, index := range sinfo.InlineUnmarshalers {
+						inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
 					}
-					if finfo.Inline == nil {
-						finfo.Inline = []int{i, finfo.Num}
-					} else {
-						finfo.Inline = append([]int{i}, finfo.Inline...)
+					for _, finfo := range sinfo.FieldsList {
+						if _, found := fieldsMap[finfo.Key]; found {
+							msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
+							return nil, errors.New(msg)
+						}
+						if finfo.Inline == nil {
+							finfo.Inline = []int{i, finfo.Num}
+						} else {
+							finfo.Inline = append([]int{i}, finfo.Inline...)
+						}
+						finfo.Id = len(fieldsList)
+						fieldsMap[finfo.Key] = finfo
+						fieldsList = append(fieldsList, finfo)
 					}
-					finfo.Id = len(fieldsList)
-					fieldsMap[finfo.Key] = finfo
-					fieldsList = append(fieldsList, finfo)
 				}
 			default:
 				//return nil, errors.New("option ,inline needs a struct value or map field")
@@ -416,6 +434,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
 		FieldsMap:  fieldsMap,
 		FieldsList: fieldsList,
 		InlineMap:  inlineMap,
+		InlineUnmarshalers: inlineUnmarshalers,
 	}
 
 	fieldMapMutex.Lock()