I'm trying to create a helper function which will take a map with string keys and return a slice of the map keys.
The problem is that I want the function to not care about the type of the map values.
For example:
stringStringMap := map[string]string{
"one": "first",
"two": "second"
}
mapKeys(stringStringMap) // ["one", "two"]
stringIntMap := map[string]int{
"one": 1,
"two": 2,
}
mapKeys(strinIntMap) // ["one", "two"]
It seems that the only way around this problem is to create two similar helpers. Something like this:
func mapKeys(m map[string]string) []string {
...
}
func mapKeys2(m map[string]int) []string {
...
}
But this seems ugly. Is this helper function I'm trying to create possible?If not, is there a good convention I should follow when writing this?
There is a third option not mentioned yet, and that is to use a type switch. This may be a good option if you know that you'll be passing a manageable number of types to the function. It would work like this:
func Keys(m interface{}) ([]string, error) {
switch t := m.(type) {
case map[string]string:
keys := make([]string, 0, len(t))
for key := range t {
keys = append(keys, key)
}
return keys, nil
case map[string]int:
keys := make([]string, 0, len(t))
for key := range t {
keys = append(keys, key)
}
return keys, nil
default:
return nil, fmt.Errorf("unknown map type: %T", m)
}
}
This still gives you a bunch of seemingly duplicate code, but at least it's all behind a single function name, and it's more efficient than reflection.
A way to solve this problem is to use "interface{}" when creating the maps and then use a type switch statement in the method i.e.
func mapKeys(m map[string]interface{}) []string {
for k,v := range m {
switch a := v.(type) {
case int:
... do int stuff
case string:
... do string stuff
}
}
}
Go does not have user specified generic parameter types, so what you are describing must be done via:
interface{}
) and uses reflection.Here is an example implementation of the second approach:
package main
import (
"fmt"
"reflect"
)
func main() {
stringStringMap := map[string]string{
"one": "first",
"two": "second",
}
fmt.Println(mapKeys(stringStringMap)) // ["one", "two"]
stringIntMap := map[string]int{
"one": 1,
"two": 2,
}
fmt.Println(mapKeys(stringIntMap)) // ["one", "two"]
}
func mapKeys(m interface{}) []string {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
panic("m is not a map")
}
if v.Type().Key().Kind() != reflect.String {
panic("m does not have a string key")
}
keys := make([]string, 0, v.Len())
for _, key := range v.MapKeys() {
keys = append(keys, key.String())
}
return keys
}