Merge pull request #60 from fatih/map-type-support
structs: add support for map and slice type
This commit is contained in:
commit
f23af605df
86
structs.go
86
structs.go
@ -115,17 +115,17 @@ func (s *Struct) FillMap(out map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// map[string]interface{} too
|
||||
n := New(val.Interface())
|
||||
n.TagName = s.TagName
|
||||
m := n.Map()
|
||||
isSubStruct = true
|
||||
if len(m) == 0 {
|
||||
finalVal = val.Interface()
|
||||
} else {
|
||||
finalVal = m
|
||||
if !tagOpts.Has("omitnested") {
|
||||
finalVal = s.nested(val)
|
||||
|
||||
v := reflect.ValueOf(val.Interface())
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Map, reflect.Struct:
|
||||
isSubStruct = true
|
||||
}
|
||||
} else {
|
||||
finalVal = val.Interface()
|
||||
@ -505,3 +505,67 @@ func IsStruct(s interface{}) bool {
|
||||
func Name(s interface{}) string {
|
||||
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
|
||||
}
|
||||
|
||||
233
structs_test.go
233
structs_test.go
@ -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) {
|
||||
type A struct {
|
||||
Name string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user