Add support for ,inline flag with struct values. This also adds the code to do inlining of maps, as supported by bson (the code was copied from mgo/bson), but it's disabled for the moment as I'll need more time to implement it.
diff --git a/decode.go b/decode.go index e7fb29b..15f4a04 100644 --- a/decode.go +++ b/decode.go
@@ -445,7 +445,13 @@ continue } if info, ok := fieldsMap[name.String()]; ok { - d.unmarshal(n.children[i+1], out.Field(info.Num)) + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) } } return true
diff --git a/decode_test.go b/decode_test.go index 89a9fdb..965032c 100644 --- a/decode_test.go +++ b/decode_test.go
@@ -317,6 +317,15 @@ B int "-" }{1, 0}, }, + + // Struct inlining + { + "a: 1\nb: 2\n", + &struct { + A int + C struct{ B int } `yaml:",inline"` + }{1, struct{ B int }{2}}, + }, } func (s *S) TestUnmarshal(c *C) {
diff --git a/encode.go b/encode.go index ad7853c..6a9c8fa 100644 --- a/encode.go +++ b/encode.go
@@ -115,7 +115,12 @@ } e.mappingv(tag, func() { for _, info := range fields.List { - value := in.Field(info.Num) + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } if info.OmitEmpty && isZero(value) { continue }
diff --git a/encode_test.go b/encode_test.go index 0d34d88..ad44241 100644 --- a/encode_test.go +++ b/encode_test.go
@@ -200,6 +200,15 @@ }{1, 2}, "a: 1\n", }, + + // Struct inlining + { + &struct { + A int + C struct{ B int } `yaml:",inline"` + }{1, struct{ B int }{2}}, + "a: 1\nb: 2\n", + }, } func (s *S) TestMarshal(c *C) {
diff --git a/goyaml.go b/goyaml.go index 1b71c8e..3981d71 100644 --- a/goyaml.go +++ b/goyaml.go
@@ -119,6 +119,10 @@ // flow Marshal using a flow style (useful for structs, // sequences and maps. // +// inline Inline the struct it's applied to, so its fields +// are processed as if they were part of the outer +// struct. +// // In addition, if the key is "-", the field is ignored. // // For example: @@ -131,7 +135,7 @@ // goyaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" // func Marshal(in interface{}) (out []byte, err error) { - //defer handleErr(&err) + defer handleErr(&err) e := newEncoder() defer e.destroy() e.marshal("", reflect.ValueOf(in)) @@ -146,8 +150,9 @@ // The code in this section was copied from gobson. type structFields struct { - Map map[string]fieldInfo - List []fieldInfo + Map map[string]fieldInfo + List []fieldInfo + InlineMap int } type fieldInfo struct { @@ -155,6 +160,7 @@ Num int OmitEmpty bool Flow bool + Inline []int } var fieldMap = make(map[reflect.Type]*structFields) @@ -176,7 +182,8 @@ n := st.NumField() fieldsMap := make(map[string]fieldInfo) - fieldsList := make([]fieldInfo, n) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 for i := 0; i != n; i++ { field := st.Field(i) if field.PkgPath != "" { @@ -193,24 +200,7 @@ continue } - // XXX Drop this after a few releases. - if s := strings.Index(tag, "/"); s >= 0 { - recommend := tag[:s] - for _, c := range tag[s+1:] { - switch c { - case 'c': - recommend += ",omitempty" - case 'f': - recommend += ",flow" - default: - msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", string([]byte{uint8(c)}), tag, st) - panic(externalPanic(msg)) - } - } - msg := fmt.Sprintf("Replace tag %q in field %s of type %s by %q", tag, field.Name, st, recommend) - panic(externalPanic(msg)) - } - + inline := false fields := strings.Split(tag, ",") if len(fields) > 1 { for _, flag := range fields[1:] { @@ -219,6 +209,8 @@ info.OmitEmpty = true case "flow": info.Flow = true + case "inline": + inline = true default: msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st) panic(externalPanic(msg)) @@ -227,6 +219,41 @@ tag = fields[0] } + if inline { + switch field.Type.Kind() { + //case reflect.Map: + // if inlineMap >= 0 { + // return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + // } + // if field.Type.Key() != reflect.TypeOf("") { + // return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + // } + // inlineMap = info.Num + case reflect.Struct: + sfields, err := getStructFields(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sfields.List { + 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...) + } + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //panic("Option ,inline needs a struct value or map field") + panic("Option ,inline needs a struct value field") + } + continue + } + if tag != "" { info.Key = tag } else { @@ -238,11 +265,11 @@ return nil, errors.New(msg) } - fieldsList[len(fieldsMap)] = info + fieldsList = append(fieldsList, info) fieldsMap[info.Key] = info } - fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]} + fields = &structFields{fieldsMap, fieldsList, inlineMap} fieldMapMutex.Lock() fieldMap[st] = fields