created flatnested tag to convert the anonymous field in a flat map (#46)

* flat fields with the new flatten tag
* added flatten tag documentation
This commit is contained in:
n3wtron 2016-05-10 14:09:22 +02:00 committed by Fatih Arslan
parent e5ca5fe90d
commit 24f3e1df2f
2 changed files with 69 additions and 2 deletions

View File

@ -52,6 +52,12 @@ func New(s interface{}) *Struct {
// // Map will panic if Animal does not implement String(). // // Map will panic if Animal does not implement String().
// Field *Animal `structs:"field,string"` // Field *Animal `structs:"field,string"`
// //
// A tag value with the option of "flatten" used in a struct field is to flatten its fields
// in the output map. Example:
//
// // The FieldStruct's fields will be flattened into the output map.
// FieldStruct time.Time `structs:"flatten"`
//
// A tag value with the option of "omitnested" stops iterating further if the type // A tag value with the option of "omitnested" stops iterating further if the type
// is a struct. Example: // is a struct. Example:
// //
@ -90,7 +96,7 @@ func (s *Struct) FillMap(out map[string]interface{}) {
for _, field := range fields { for _, field := range fields {
name := field.Name name := field.Name
val := s.value.FieldByName(name) val := s.value.FieldByName(name)
isSubStruct := false
var finalVal interface{} var finalVal interface{}
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName)) tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
@ -115,6 +121,7 @@ func (s *Struct) FillMap(out map[string]interface{}) {
n := New(val.Interface()) n := New(val.Interface())
n.TagName = s.TagName n.TagName = s.TagName
m := n.Map() m := n.Map()
isSubStruct = true
if len(m) == 0 { if len(m) == 0 {
finalVal = val.Interface() finalVal = val.Interface()
} else { } else {
@ -132,9 +139,15 @@ func (s *Struct) FillMap(out map[string]interface{}) {
continue continue
} }
if isSubStruct && (tagOpts.Has("flatten")) {
for k := range finalVal.(map[string]interface{}) {
out[k] = finalVal.(map[string]interface{})[k]
}
} else {
out[name] = finalVal out[name] = finalVal
} }
} }
}
// Values converts the given s struct's field values to a []interface{}. A // Values converts the given s struct's field values to a []interface{}. A
// struct tag with the content of "-" ignores the that particular field. // struct tag with the content of "-" ignores the that particular field.

View File

@ -296,6 +296,60 @@ func TestMap_Anonymous(t *testing.T) {
} }
} }
func TestMap_Flatnested(t *testing.T) {
type A struct {
Name string
}
a := A{Name: "example"}
type B struct {
A `structs:",flatten"`
C int
}
b := &B{C: 123}
b.A = a
m := Map(b)
_, ok := m["A"].(map[string]interface{})
if ok {
t.Error("Embedded A struct with tag flatten has to be flat in the map")
}
expectedMap := map[string]interface{}{"Name": "example", "C": 123}
if !reflect.DeepEqual(m, expectedMap) {
t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m)
}
}
func TestMap_FlatnestedOverwrite(t *testing.T) {
type A struct {
Name string
}
a := A{Name: "example"}
type B struct {
A `structs:",flatten"`
Name string
C int
}
b := &B{C: 123, Name: "bName"}
b.A = a
m := Map(b)
_, ok := m["A"].(map[string]interface{})
if ok {
t.Error("Embedded A struct with tag flatten has to be flat in the map")
}
expectedMap := map[string]interface{}{"Name": "bName", "C": 123}
if !reflect.DeepEqual(m, expectedMap) {
t.Errorf("The exprected map %+v does't correspond to %+v", expectedMap, m)
}
}
func TestMap_TimeField(t *testing.T) { func TestMap_TimeField(t *testing.T) {
type A struct { type A struct {
CreatedAt time.Time CreatedAt time.Time