How can I reverse map using reflection

Viewed 348

I am studying reflect in Go and trying to implement function which get map and return another map, where keys will be values and values will be keys.

Example:

m := map[string]int{"one": 1, "two": 2}
fmt.Println(ReverseMap(m)) // {1: "one", 2: "two"}

Here is my code:

func ReverseMap(in interface{}) interface{} {

    var out reflect.Value
    v := reflect.ValueOf(in)
    if v.Kind() == reflect.Map {
        for idx, key := range v.MapKeys() {
            value := v.MapIndex(key)
            if idx == 0 {
                mapType := reflect.MapOf(reflect.TypeOf(value), reflect.TypeOf(key))
                out = reflect.MakeMap(mapType)
            }
            out.SetMapIndex(value, key)
        }
    }
    return out
}

This code panic with error:

panic: reflect.Value.SetMapIndex: value of type int is not assignable to type reflect.Value

I think the reason of this error is the declaration of out variable, but I don't know how to declare it correctly, if I don't know the type of this variable.

How can I fix this error?

1 Answers

The key and value are of type reflect.Value, so passing them to reflect.TypeOf() will not return the type descriptors of the key and value types of the map (string and int), but instead the type descriptor of the reflect.Value type itself.

Instead simply call their Value.Type() method:

mapType := reflect.MapOf(value.Type(), key.Type())

With this it'll (almost) work and print (try it on the Go Playground):

map[1:one 2:two]

I wrote "almost" because you're returning a reflect.Value, not a map. But if a reflect.Value is passed to the fmt package, it prints the value wrapped inside it:

If the operand is a reflect.Value, the operand is replaced by the concrete value that it holds, and printing continues with the next rule.

So you should call Value.Interface() on out before returning it.

It's easier to return early if the kind is not map, so you can create the map right after that:

func ReverseMap(in interface{}) interface{} {
    v := reflect.ValueOf(in)
    if v.Kind() != reflect.Map {
        return nil
    }

    mapType := reflect.MapOf(v.Type().Elem(), v.Type().Key())
    out := reflect.MakeMap(mapType)

    for _, key := range v.MapKeys() {
        out.SetMapIndex(v.MapIndex(key), key)
    }

    return out.Interface()
}

Try this variant on the Go Playground.

Another approach may be using Value.MapRange():

for iter := v.MapRange(); iter.Next(); {
    out.SetMapIndex(iter.Value(), iter.Key())
}

Try this variant on the Go Playground.

Related