From 24f3e1df2f5ffab5696aaa95cf0b8dea167ef084 Mon Sep 17 00:00:00 2001 From: n3wtron Date: Tue, 10 May 2016 14:09:22 +0200 Subject: [PATCH] created flatnested tag to convert the anonymous field in a flat map (#46) * flat fields with the new flatten tag * added flatten tag documentation --- structs.go | 17 ++++++++++++++-- structs_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/structs.go b/structs.go index 408d50f..b88ec62 100644 --- a/structs.go +++ b/structs.go @@ -52,6 +52,12 @@ func New(s interface{}) *Struct { // // Map will panic if Animal does not implement 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 // is a struct. Example: // @@ -90,7 +96,7 @@ func (s *Struct) FillMap(out map[string]interface{}) { for _, field := range fields { name := field.Name val := s.value.FieldByName(name) - + isSubStruct := false var finalVal interface{} 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.TagName = s.TagName m := n.Map() + isSubStruct = true if len(m) == 0 { finalVal = val.Interface() } else { @@ -132,7 +139,13 @@ func (s *Struct) FillMap(out map[string]interface{}) { continue } - out[name] = finalVal + if isSubStruct && (tagOpts.Has("flatten")) { + for k := range finalVal.(map[string]interface{}) { + out[k] = finalVal.(map[string]interface{})[k] + } + } else { + out[name] = finalVal + } } } diff --git a/structs_test.go b/structs_test.go index 30e3115..d7ea11d 100644 --- a/structs_test.go +++ b/structs_test.go @@ -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) { type A struct { CreatedAt time.Time