structs: add support for map and slice type

Fixes #58
This commit is contained in:
Fatih Arslan 2016-06-11 15:08:39 +03:00
parent 5ada2f449b
commit f7e369b900
2 changed files with 308 additions and 11 deletions

View File

@ -115,17 +115,17 @@ func (s *Struct) FillMap(out map[string]interface{}) {
} }
} }
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { if !tagOpts.Has("omitnested") {
// look out for embedded structs, and convert them to a finalVal = s.nested(val)
// map[string]interface{} too
n := New(val.Interface()) v := reflect.ValueOf(val.Interface())
n.TagName = s.TagName if v.Kind() == reflect.Ptr {
m := n.Map() v = v.Elem()
isSubStruct = true }
if len(m) == 0 {
finalVal = val.Interface() switch v.Kind() {
} else { case reflect.Map, reflect.Struct:
finalVal = m isSubStruct = true
} }
} else { } else {
finalVal = val.Interface() finalVal = val.Interface()
@ -505,3 +505,67 @@ func IsStruct(s interface{}) bool {
func Name(s interface{}) string { func Name(s interface{}) string {
return New(s).Name() return New(s).Name()
} }
// nested retrieves recursively all types for the given value and returns the
// nested value.
func (s *Struct) nested(val reflect.Value) interface{} {
var finalVal interface{}
v := reflect.ValueOf(val.Interface())
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
n := New(val.Interface())
n.TagName = s.TagName
m := n.Map()
// do not add the converted value if there are no exported fields, ie:
// time.Time
if len(m) == 0 {
finalVal = val.Interface()
} else {
finalVal = m
}
case reflect.Map:
v := val.Type().Elem()
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// only iterate over struct types, ie: map[string]StructType,
// map[string][]StructType,
if v.Kind() == reflect.Struct ||
(v.Kind() == reflect.Slice && v.Elem().Kind() == reflect.Struct) {
m := make(map[string]interface{}, val.Len())
for _, k := range val.MapKeys() {
m[k.String()] = s.nested(val.MapIndex(k))
}
finalVal = m
break
}
// TODO(arslan): should this be optional?
finalVal = val.Interface()
case reflect.Slice, reflect.Array:
// TODO(arslan): should this be optional?
// do not iterate of non struct types, just pass the value. Ie: []int,
// []string, co... We only iterate further if it's a struct.
if val.Type().Elem().Kind() != reflect.Struct {
finalVal = val.Interface()
break
}
slices := make([]interface{}, val.Len(), val.Len())
for x := 0; x < val.Len(); x++ {
slices[x] = s.nested(val.Index(x))
}
finalVal = slices
default:
finalVal = val.Interface()
}
return finalVal
}

View File

@ -268,6 +268,239 @@ func TestMap_Nested(t *testing.T) {
} }
} }
func TestMap_NestedMapWithStructValues(t *testing.T) {
type A struct {
Name string
}
type B struct {
A map[string]*A
}
a := &A{Name: "example"}
b := &B{
A: map[string]*A{
"example_key": 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.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["A"])
}
example := in["example_key"].(map[string]interface{})
if name := example["Name"].(string); name != "example" {
t.Errorf("Map nested struct's name field should give example, got: %s", name)
}
}
func TestMap_NestedMapWithStringValues(t *testing.T) {
type B struct {
Foo map[string]string
}
type A struct {
B *B
}
b := &B{
Foo: map[string]string{
"example_key": "example",
},
}
a := &A{B: b}
m := Map(a)
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
t.Errorf("Map should return a map type, got: %v", typ)
}
in, ok := m["B"].(map[string]interface{})
if !ok {
t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
}
foo := in["Foo"].(map[string]string)
if name := foo["example_key"]; name != "example" {
t.Errorf("Map nested struct's name field should give example, got: %s", name)
}
}
func TestMap_NestedMapWithInterfaceValues(t *testing.T) {
type B struct {
Foo map[string]interface{}
}
type A struct {
B *B
}
b := &B{
Foo: map[string]interface{}{
"example_key": "example",
},
}
a := &A{B: b}
m := Map(a)
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
t.Errorf("Map should return a map type, got: %v", typ)
}
in, ok := m["B"].(map[string]interface{})
if !ok {
t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
}
foo := in["Foo"].(map[string]interface{})
if name := foo["example_key"]; name != "example" {
t.Errorf("Map nested struct's name field should give example, got: %s", name)
}
}
func TestMap_NestedMapWithSliceIntValues(t *testing.T) {
type B struct {
Foo map[string][]int
}
type A struct {
B *B
}
b := &B{
Foo: map[string][]int{
"example_key": []int{80},
},
}
a := &A{B: b}
m := Map(a)
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
t.Errorf("Map should return a map type, got: %v", typ)
}
in, ok := m["B"].(map[string]interface{})
if !ok {
t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
}
foo := in["Foo"].(map[string][]int)
if name := foo["example_key"]; name[0] != 80 {
t.Errorf("Map nested struct's name field should give example, got: %s", name)
}
}
func TestMap_NestedMapWithSliceStructValues(t *testing.T) {
type address struct {
Country string `structs:"country"`
}
type B struct {
Foo map[string][]address
}
type A struct {
B *B
}
b := &B{
Foo: map[string][]address{
"example_key": []address{
{Country: "Turkey"},
},
},
}
a := &A{B: b}
m := Map(a)
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
t.Errorf("Map should return a map type, got: %v", typ)
}
in, ok := m["B"].(map[string]interface{})
if !ok {
t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
}
foo := in["Foo"].(map[string]interface{})
addresses := foo["example_key"].([]interface{})
addr, ok := addresses[0].(map[string]interface{})
if !ok {
t.Errorf("Nested type of map should be of type map[string]interface{}, have %T", m["B"])
}
if _, exists := addr["country"]; !exists {
t.Errorf("Expecting country, but found Country")
}
}
func TestMap_NestedSliceWithStructValues(t *testing.T) {
type address struct {
Country string `structs:"customCountryName"`
}
type person struct {
Name string `structs:"name"`
Addresses []address `structs:"addresses"`
}
p := person{
Name: "test",
Addresses: []address{
address{Country: "England"},
address{Country: "Italy"},
},
}
mp := Map(p)
mpAddresses := mp["addresses"].([]interface{})
if _, exists := mpAddresses[0].(map[string]interface{})["Country"]; exists {
t.Errorf("Expecting customCountryName, but found Country")
}
if _, exists := mpAddresses[0].(map[string]interface{})["customCountryName"]; !exists {
t.Errorf("customCountryName key not found")
}
}
func TestMap_NestedSliceWithIntValues(t *testing.T) {
type person struct {
Name string `structs:"name"`
Ports []int `structs:"ports"`
}
p := person{
Name: "test",
Ports: []int{80},
}
m := Map(p)
ports, ok := m["ports"].([]int)
if !ok {
t.Errorf("Nested type of map should be of type []int, have %T", m["ports"])
}
if ports[0] != 80 {
t.Errorf("Map nested struct's ports field should give 80, got: %v", ports)
}
}
func TestMap_Anonymous(t *testing.T) { func TestMap_Anonymous(t *testing.T) {
type A struct { type A struct {
Name string Name string