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