From 4d9abb0de1286a5d4a53743004f7d6fe96552ae1 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Wed, 30 Jul 2014 18:49:46 +0300 Subject: [PATCH] structure: check for nested structs, closes #6 --- structure.go | 51 +++++++++++++++++++--- structure_test.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 6 deletions(-) diff --git a/structure.go b/structure.go index 1c3298f..34c7698 100644 --- a/structure.go +++ b/structure.go @@ -26,6 +26,16 @@ func Map(s interface{}) map[string]interface{} { for i, field := range fields { name := field.Name + val := v.Field(i) + + var finalVal interface{} + if val.Kind() == reflect.Struct { + // look out for embedded structs, and convert them to a + // map[string]interface{} too + finalVal = Map(val.Interface()) + } else { + finalVal = val.Interface() + } // override if the user passed a structure tag value // ignore if the user passed the "-" value @@ -33,7 +43,7 @@ func Map(s interface{}) map[string]interface{} { name = tag } - out[name] = v.Field(i).Interface() + out[name] = finalVal } return out @@ -51,9 +61,18 @@ func Map(s interface{}) map[string]interface{} { func Values(s interface{}) []interface{} { v, fields := strctInfo(s) - t := make([]interface{}, len(fields)) + t := make([]interface{}, 0) for i := range fields { - t[i] = v.Field(i).Interface() + val := v.Field(i) + if val.Kind() == reflect.Struct { + // look out for embedded structs, and convert them to a + // []interface{} to be added to the final values slice + for _, embeddedVal := range Values(val.Interface()) { + t = append(t, embeddedVal) + } + } else { + t = append(t, val.Interface()) + } } return t @@ -73,6 +92,16 @@ func IsValid(s interface{}) bool { v, fields := strctInfo(s) for i := range fields { + val := v.Field(i) + if val.Kind() == reflect.Struct { + ok := IsValid(val.Interface()) + if !ok { + return false + } + + continue + } + // zero value of the given field, such as "" for string, 0 for int zero := reflect.Zero(v.Field(i).Type()).Interface() @@ -96,11 +125,21 @@ func IsValid(s interface{}) bool { // Note that only exported fields of a struct can be accessed, non exported // fields will be neglected. It panics if s's kind is not struct. func Fields(s interface{}) []string { - _, fields := strctInfo(s) + v, fields := strctInfo(s) - keys := make([]string, len(fields)) + keys := make([]string, 0) for i, field := range fields { - keys[i] = field.Name + + val := v.Field(i) + if val.Kind() == reflect.Struct { + // look out for embedded structs, and convert them to a + // []string to be added to the final values slice + for _, embeddedVal := range Fields(val.Interface()) { + keys = append(keys, embeddedVal) + } + } + + keys = append(keys, field.Name) } return keys diff --git a/structure_test.go b/structure_test.go index d27daa1..4828ce8 100644 --- a/structure_test.go +++ b/structure_test.go @@ -89,6 +89,33 @@ func TestMap_Tag(t *testing.T) { } +func TestMap_Embedded(t *testing.T) { + type A struct { + Name string + } + a := A{Name: "example"} + + type B struct { + A A + } + b := &B{A: a} + + m := Map(b) + + if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { + t.Errorf("Map should return a map type, got: %v", typ) + } + + in, ok := m["A"].(map[string]interface{}) + if !ok { + t.Error("Embedded structs is not available in the map") + } + + if name := in["Name"].(string); name != "example" { + t.Error("Embedded A struct's Name field should give example, got: %s", name) + } +} + func TestStruct(t *testing.T) { var T = struct{}{} @@ -135,6 +162,36 @@ func TestValues(t *testing.T) { } } +func TestValues_Embedded(t *testing.T) { + type A struct { + Name string + } + a := A{Name: "example"} + + type B struct { + A A + C int + } + b := &B{A: a, C: 123} + + s := Values(b) + + inSlice := func(val interface{}) bool { + for _, v := range s { + if reflect.DeepEqual(v, val) { + return true + } + } + return false + } + + for _, val := range []interface{}{"example", 123} { + if !inSlice(val) { + t.Errorf("Values should have the value %v", val) + } + } +} + func TestFields(t *testing.T) { var T = struct { A string @@ -168,6 +225,36 @@ func TestFields(t *testing.T) { } } +func TestFields_Embedded(t *testing.T) { + type A struct { + Name string + } + a := A{Name: "example"} + + type B struct { + A A + C int + } + b := &B{A: a, C: 123} + + s := Fields(b) + + inSlice := func(val interface{}) bool { + for _, v := range s { + if reflect.DeepEqual(v, val) { + return true + } + } + return false + } + + for _, val := range []interface{}{"Name", "A", "C"} { + if !inSlice(val) { + t.Errorf("Fields should have the value %v", val) + } + } +} + func TestIsValid(t *testing.T) { var T = struct { A string @@ -197,6 +284,26 @@ func TestIsValid(t *testing.T) { } } +func TestIsValid_Embedded(t *testing.T) { + type A struct { + Name string + D string + } + a := A{Name: "example"} + + type B struct { + A A + C int + } + b := &B{A: a, C: 123} + + ok := IsValid(b) + if ok { + t.Error("IsValid should return false because D is not initialized") + } + +} + func TestName(t *testing.T) { type Foo struct { A string