Let's imagine I have a map: map[string]string
. I would like to get the list of sorted keys for this map. So I could do something like this:
func SortedMapKeys(m map[string]string) (keyList []string) {
for key := range m {
keyList = append(keyList, key)
}
sort.Strings(keyList)
return
}
Then I will have another map of type map[string]bool
. I would like to get it's keys also. But the problem is that function SortedMapKeys accepts a map[string]string
argument. So I need to write exactly the same function, with the only one difference - it will accept a map[string]bool
.
For obvious reasons it's not an option. If one day I would like to change the logic of how I get and sort my keys, I will need to track and update all these functions. Also, I will have to write the same unit tests for all these functions which actually do the same thing since their bodies are 100% equal (code duplicate).
Is there any way to create a generic function which could accept an map[string]
of whatever?
Since map[string]bool
and map[string]string
and map[string]Whatever
are all distinct types, the only way to create a single function to sort the keys of all possible map[string]*
types is via reflection.
func SortedMapKeys(m interface{}) (keyList []string) {
keys := reflect.ValueOf(m).MapKeys()
for _, key := range keys {
keyList = append(keyList, key.Interface().(string))
}
sort.Strings(keyList)
return
}
For an in-between solution, since there are probably only a few combinations of types you're concerned with, you can use a type switch to extract the keys
func SortedMapKeys(m interface{}) (keyList []string) {
switch m := m.(type) {
case map[string]string:
for k := range m {
keyList = append(keyList, k)
}
case map[string]bool:
for k := range m {
keyList = append(keyList, k)
}
default:
panic("unknown map type")
}
sort.Strings(keyList)
return
}
Here's my 0.02$. Since keys extracting logic is unlikely to change and you want to keep everything in one place you can create variant and choose non-nil map from it:
type MapVariant struct {
Bool map[string]bool
String map[string]string
}
func SortedMapKeys(variant MapVariant) (keyList []string) {
if variant.String != nil {
for k := range variant.String {
keyList = append(keyList, k)
}
goto SORT
}
if variant.Bool != nil {
for k := range variant.Bool {
keyList = append(keyList, k)
}
goto SORT
}
SORT:
sort.Strings(keyList)
return
}
Of course you can avoid goto statements by adding more conditions but I personally find it clearer.
Then you can use the function like:
SortedMapKeys(MapVariant{
Bool: map[string]bool{"a": true, "b": false}
})
SortedMapKeys(MapVariant{
String: map[string]string{"c": "v1", "b": "v2"}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With