diff --git a/decode.go b/decode.go index ec9d271..085cddc 100644 --- a/decode.go +++ b/decode.go @@ -607,6 +607,15 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { } name := settableValueOf("") l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + for i := 0; i < l; i += 2 { ni := n.children[i] if isMerge(ni) { @@ -624,6 +633,13 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { field = out.FieldByIndex(info.Inline) } d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + inlineMap.SetMapIndex(name, value) } } return true diff --git a/decode_test.go b/decode_test.go index b91a060..179f2ce 100644 --- a/decode_test.go +++ b/decode_test.go @@ -473,6 +473,15 @@ var unmarshalTests = []struct { }{1, inlineB{2, inlineC{3}}}, }, + // Map inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + }, + // bug 1243827 { "a: -b_c", diff --git a/encode.go b/encode.go index b7edc79..84f8499 100644 --- a/encode.go +++ b/encode.go @@ -2,6 +2,7 @@ package yaml import ( "encoding" + "fmt" "reflect" "regexp" "sort" @@ -164,6 +165,22 @@ func (e *encoder) structv(tag string, in reflect.Value) { e.flow = info.Flow e.marshal("", value) } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } }) } diff --git a/encode_test.go b/encode_test.go index d453f5c..e340f58 100644 --- a/encode_test.go +++ b/encode_test.go @@ -238,6 +238,15 @@ var marshalTests = []struct { "a: 1\nb: 2\nc: 3\n", }, + // Map inlining + { + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + "a: 1\nb: 2\nc: 3\n", + }, + // Duration { map[string]time.Duration{"a": 3 * time.Second}, @@ -312,6 +321,12 @@ var marshalErrorTests = []struct { inlineB ",inline" }{1, inlineB{2, inlineC{3}}}, panic: `Duplicated key 'b' in struct struct \{ B int; .*`, +}, { + value: &struct { + A int + B map[string]int ",inline" + }{1, map[string]int{"a": 2}}, + panic: `Can't have key "a" in inlined map; conflicts with struct field`, }} func (s *S) TestMarshalErrors(c *C) { diff --git a/yaml.go b/yaml.go index e3e01ed..6af88c0 100644 --- a/yaml.go +++ b/yaml.go @@ -119,9 +119,10 @@ func Unmarshal(in []byte, out interface{}) (err error) { // 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. +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. // // In addition, if the key is "-", the field is ignored. // @@ -255,15 +256,14 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { if inline { switch field.Type.Kind() { - // TODO: Implement support for inline maps. - //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.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: sinfo, err := getStructInfo(field.Type) if err != nil {