Search code examples
gotypesreflectioninterfacenumbers

Is there a good way to make number type fit "method.Call" in golang?


Code Go Playground :

package main

import (
    "fmt"
    "reflect"
)

func (s StructWithManyMethods) Func1(a int, b uint, c float64) {
    fmt.Printf("func:Func1 a:%d b:%d c:%f \n", a, b, c)
}
func (s StructWithManyMethods) Func2(a string, b int, c int, d int) {
    fmt.Printf("func:Func2 a:%s b:%d c:%d d:%d\n", a, b, c, d)
}

type StructWithManyMethods struct {
}

func (s StructWithManyMethods) CallMethod(n string, p []interface{}) {
    method := reflect.ValueOf(s).MethodByName(n)
    methodType := method.Type()
    for i := 0; i < methodType.NumIn(); i++ {
        in := methodType.In(i)
        switch in.Kind() {
        case reflect.Float32:
            switch v := p[i].(type) {
            case float64:
                p[i] = float32(v)
            case float32:
                p[i] = float32(v)
            case int:
                p[i] = float32(v)
            case uint:
                p[i] = float32(v)
            case int8:
                p[i] = float32(v)
            case uint8:
                p[i] = float32(v)
            case int16:
                p[i] = float32(v)
            case uint16:
                p[i] = float32(v)
            case int32:
                p[i] = float32(v)
            case uint32:
                p[i] = float32(v)
            case int64:
                p[i] = float32(v)
            case uint64:
                p[i] = float32(v)
            }
        case reflect.Float64:
            switch v := p[i].(type) {
            case float64:
                p[i] = float64(v)
            case float32:
                p[i] = float64(v)
            case int:
                p[i] = float64(v)
            case uint:
                p[i] = float64(v)
            case int8:
                p[i] = float64(v)
            case uint8:
                p[i] = float64(v)
            case int16:
                p[i] = float64(v)
            case uint16:
                p[i] = float64(v)
            case int32:
                p[i] = float64(v)
            case uint32:
                p[i] = float64(v)
            case int64:
                p[i] = float64(v)
            case uint64:
                p[i] = float64(v)
            }
        case reflect.Int:
            switch v := p[i].(type) {
            case float64:
                p[i] = int(v)
            case float32:
                p[i] = int(v)
            case int:
                p[i] = int(v)
            case uint:
                p[i] = int(v)
            case int8:
                p[i] = int(v)
            case uint8:
                p[i] = int(v)
            case int16:
                p[i] = int(v)
            case uint16:
                p[i] = int(v)
            case int32:
                p[i] = int(v)
            case uint32:
                p[i] = int(v)
            case int64:
                p[i] = int(v)
            case uint64:
                p[i] = int(v)
            }
        case reflect.Uint:
            switch v := p[i].(type) {
            case float64:
                p[i] = uint(v)
            case float32:
                p[i] = uint(v)
            case int:
                p[i] = uint(v)
            case uint:
                p[i] = uint(v)
            case int8:
                p[i] = uint(v)
            case uint8:
                p[i] = uint(v)
            case int16:
                p[i] = uint(v)
            case uint16:
                p[i] = uint(v)
            case int32:
                p[i] = uint(v)
            case uint32:
                p[i] = uint(v)
            case int64:
                p[i] = uint(v)
            case uint64:
                p[i] = uint(v)
            }
        case reflect.Int8:
            switch v := p[i].(type) {
            case float64:
                p[i] = int8(v)
            case float32:
                p[i] = int8(v)
            case int:
                p[i] = int8(v)
            case uint:
                p[i] = int8(v)
            case int8:
                p[i] = int8(v)
            case uint8:
                p[i] = int8(v)
            case int16:
                p[i] = int8(v)
            case uint16:
                p[i] = int8(v)
            case int32:
                p[i] = int8(v)
            case uint32:
                p[i] = int8(v)
            case int64:
                p[i] = int8(v)
            case uint64:
                p[i] = int8(v)
            }
        case reflect.Uint8:
            switch v := p[i].(type) {
            case float64:
                p[i] = uint8(v)
            case float32:
                p[i] = uint8(v)
            case int:
                p[i] = uint8(v)
            case uint:
                p[i] = uint8(v)
            case int8:
                p[i] = uint8(v)
            case uint8:
                p[i] = uint8(v)
            case int16:
                p[i] = uint8(v)
            case uint16:
                p[i] = uint8(v)
            case int32:
                p[i] = uint8(v)
            case uint32:
                p[i] = uint8(v)
            case int64:
                p[i] = uint8(v)
            case uint64:
                p[i] = uint8(v)
            }
        case reflect.Int16:
            switch v := p[i].(type) {
            case float64:
                p[i] = int16(v)
            case float32:
                p[i] = int16(v)
            case int:
                p[i] = int16(v)
            case uint:
                p[i] = int16(v)
            case int8:
                p[i] = int16(v)
            case uint8:
                p[i] = int16(v)
            case int16:
                p[i] = int16(v)
            case uint16:
                p[i] = int16(v)
            case int32:
                p[i] = int16(v)
            case uint32:
                p[i] = int16(v)
            case int64:
                p[i] = int16(v)
            case uint64:
                p[i] = int16(v)
            }
        case reflect.Uint16:
            switch v := p[i].(type) {
            case float64:
                p[i] = uint16(v)
            case float32:
                p[i] = uint16(v)
            case int:
                p[i] = uint16(v)
            case uint:
                p[i] = uint16(v)
            case int8:
                p[i] = uint16(v)
            case uint8:
                p[i] = uint16(v)
            case int16:
                p[i] = uint16(v)
            case uint16:
                p[i] = uint16(v)
            case int32:
                p[i] = uint16(v)
            case uint32:
                p[i] = uint16(v)
            case int64:
                p[i] = uint16(v)
            case uint64:
                p[i] = uint16(v)
            }
        case reflect.Int32:
            switch v := p[i].(type) {
            case float64:
                p[i] = int32(v)
            case float32:
                p[i] = int32(v)
            case int:
                p[i] = int32(v)
            case uint:
                p[i] = int32(v)
            case int8:
                p[i] = int32(v)
            case uint8:
                p[i] = int32(v)
            case int16:
                p[i] = int32(v)
            case uint16:
                p[i] = int32(v)
            case int32:
                p[i] = int32(v)
            case uint32:
                p[i] = int32(v)
            case int64:
                p[i] = int32(v)
            case uint64:
                p[i] = int32(v)
            }
        case reflect.Uint32:
            switch v := p[i].(type) {
            case float64:
                p[i] = uint32(v)
            case float32:
                p[i] = uint32(v)
            case int:
                p[i] = uint32(v)
            case uint:
                p[i] = uint32(v)
            case int8:
                p[i] = uint32(v)
            case uint8:
                p[i] = uint32(v)
            case int16:
                p[i] = uint32(v)
            case uint16:
                p[i] = uint32(v)
            case int32:
                p[i] = uint32(v)
            case uint32:
                p[i] = uint32(v)
            case int64:
                p[i] = uint32(v)
            case uint64:
                p[i] = uint32(v)
            }
        case reflect.Int64:
            switch v := p[i].(type) {
            case float64:
                p[i] = int64(v)
            case float32:
                p[i] = int64(v)
            case int:
                p[i] = int64(v)
            case uint:
                p[i] = int64(v)
            case int8:
                p[i] = int64(v)
            case uint8:
                p[i] = int64(v)
            case int16:
                p[i] = int64(v)
            case uint16:
                p[i] = int64(v)
            case int32:
                p[i] = int64(v)
            case uint32:
                p[i] = int64(v)
            case int64:
                p[i] = int64(v)
            case uint64:
                p[i] = int64(v)
            }
        case reflect.Uint64:
            switch v := p[i].(type) {
            case float64:
                p[i] = uint64(v)
            case float32:
                p[i] = uint64(v)
            case int:
                p[i] = uint64(v)
            case uint:
                p[i] = uint64(v)
            case int8:
                p[i] = uint64(v)
            case uint8:
                p[i] = uint64(v)
            case int16:
                p[i] = uint64(v)
            case uint16:
                p[i] = uint64(v)
            case int32:
                p[i] = uint64(v)
            case uint32:
                p[i] = uint64(v)
            case int64:
                p[i] = uint64(v)
            case uint64:
                p[i] = uint64(v)
            }
        }
    }
    parameterValues := make([]reflect.Value, 0)
    for _, e := range p {
        parameterValues = append(parameterValues, reflect.ValueOf(e))
    }
    method.Call(parameterValues)
}

func main() {
    var s StructWithManyMethods
    s.CallMethod("Func1", []interface{}{1.0, 2.0, 3})
    s.CallMethod("Func2", []interface{}{"test", 1, 2, 3.0})
}

Output:

func:Func1 a:1 b:2 c:3.000000 
func:Func2 a:test b:1 c:2 d:3

Because I need to dynamically call methods, but the parameters come from different data formats, which causes the need to convert numerical types. For example, when parsing JSON in Golang, uncertain number types are treated as float64 although it may should be int.

The code looks so bad,but it works well.

I wonder if there is a better way to do this.


Solution

  • Use the reflect API to convert the values.

    func (s StructWithManyMethods) CallMethod(n string, p []interface{}) error {
        method := reflect.ValueOf(s).MethodByName(n)
        methodType := method.Type()
        parameterValues := make([]reflect.Value, methodType.NumIn())
        if len(p) < len(parameterValues) {
            return fmt.Errorf("expected %d parameters, got %d", len(parameterValues), len(p))
        }
        for i := range parameterValues {
            in := methodType.In(i)
            v := reflect.ValueOf(p[i])
            if !v.CanConvert(in) {
                return fmt.Errorf("cannot convert p[%d] from %s to %s", i, v.Type(), in)
            }
            parameterValues[i] = reflect.ValueOf(p[i]).Convert(in)
        }
        method.Call(parameterValues)
        return nil
    }
    

    To prevent a panic, this code checks whether the conversion is allowed before converting.

    Run the code on the PlayGround!