structure: check for nested structs, closes #6

This commit is contained in:
Fatih Arslan 2014-07-30 18:49:46 +03:00
parent cda09b4aa4
commit 4d9abb0de1
2 changed files with 152 additions and 6 deletions

View File

@ -26,6 +26,16 @@ func Map(s interface{}) map[string]interface{} {
for i, field := range fields { for i, field := range fields {
name := field.Name 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 // override if the user passed a structure tag value
// ignore if the user passed the "-" value // ignore if the user passed the "-" value
@ -33,7 +43,7 @@ func Map(s interface{}) map[string]interface{} {
name = tag name = tag
} }
out[name] = v.Field(i).Interface() out[name] = finalVal
} }
return out return out
@ -51,9 +61,18 @@ func Map(s interface{}) map[string]interface{} {
func Values(s interface{}) []interface{} { func Values(s interface{}) []interface{} {
v, fields := strctInfo(s) v, fields := strctInfo(s)
t := make([]interface{}, len(fields)) t := make([]interface{}, 0)
for i := range fields { 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 return t
@ -73,6 +92,16 @@ func IsValid(s interface{}) bool {
v, fields := strctInfo(s) v, fields := strctInfo(s)
for i := range fields { 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 value of the given field, such as "" for string, 0 for int
zero := reflect.Zero(v.Field(i).Type()).Interface() 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 // 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. // fields will be neglected. It panics if s's kind is not struct.
func Fields(s interface{}) []string { 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 { 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 return keys

View File

@ -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) { func TestStruct(t *testing.T) {
var T = struct{}{} 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) { func TestFields(t *testing.T) {
var T = struct { var T = struct {
A string 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) { func TestIsValid(t *testing.T) {
var T = struct { var T = struct {
A string 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) { func TestName(t *testing.T) {
type Foo struct { type Foo struct {
A string A string