2

This is a followup question to Function and argument unmarshalling in go?.

Given the following interface variable.

var args interface{}

Assuming as an example it contained these bytes:

[[74 111 104 110] [32 97 110 100 32 74 97 110 101]]

i.e. two strings "John", " and Jane"

And a function value obtained by MethodByName

f := reflect.ValueOf(s.functions).MethodByName("Hello")
if f.IsValid() {
  val := reflect.ValueOf(args)
  //  Do some kind of conversion...

  result := f.Call(val) // This won't compile. Wrong type.  How do I massage args of type interface{} into what's expected by the call.

}

I don't particularly care if it fails. I will capture the failure of the call with recover.

Here is a bit more detail:

var req struct {
    Ver      int
    MsgID    int
    FuncName string
    Args     interface{}
}

dec := codec.NewDecoder(frame, s.h)

err = dec.Decode(&req)
if err != nil {
    s.log.Println(err)
    break
}

fmt.Println("New Msg:")
fmt.Printf(" ver  : %d\n", req.Ver)
fmt.Printf(" id   : %d\n", req.MsgID)
fmt.Printf(" func : %s\n", req.FuncName)

f := reflect.ValueOf(s.functions).MethodByName(req.FuncName)
if f.IsValid() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()

    fmt.Println("Function is: ", req.FuncName)
    var callArgs []reflect.Value
    args := reflect.ValueOf(req.Args)
    t := f.Type()
    for i := 0; i < t.NumIn(); i++ {
        t := t.In(i)
        v := reflect.New(t).Elem()
        if i < args.Len() {
            // Convert arg to type of v and set.
            arg := args.Index(i)
            switch t.Kind() {
            case reflect.String:
                v.SetString(string(reflect.Value.Bytes(arg)))
            case reflect.Slice:
                if t.Elem() == reflect.TypeOf(byte(0)) {
                    v.SetBytes(reflect.Value.Bytes(arg))
                } else {
                    panic("not supported")
                }
            case reflect.Int:
                //i, err := strconv.ParseInt(string(arg), 10, 0)
                // if err != nil {
                //  panic("bad int")
                // }
                // v.SetInt(i)
            default:
                panic("not supported")
            }
        }
        // Collect arguments for the call below.
        callArgs = append(callArgs, v)
    }
    result := f.Call(callArgs)
    fmt.Println(result)

    val := reflect.ValueOf(req.Args)
    a := []reflect.Value{val}

    r := f.Call(a)
    fmt.Println("Returned", r[0], r[1])
}

Outputs:

New Msg: ver : 2 id : 1 func : Hello Function is: Hello Recovered in f reflect: call of reflect.Value.Bytes on interface Value

Note: This is an RPC API. I have a function name (see link at top of question) and some arguments that are passed in as an array. The arguments in my example here are strings but could anything that you could pass to a function. It's whatever the function requires. It's generic.

e.g.

Hello(name ...string) string

or

Add(n ...int) int

or

DoSomething(a string, b int, c bool)

etc

i.e. I'm unmarshalling the arguments and I don't know what they are. Except that I do know they will be passed into a slice and thrown into a variable Args which has the type interface{} I hope this makes sense now

3
  • If Hello takes a single argument then use vals := []reflect.Value{reflect.ValueOf(args)}, if, on the other hand, Hello takes two byte slices you'll have to first type assert the args interface{} value to get the individual arguments, call reflect.ValueOf on them, put them into a []reflect.Value{} slice and pass them to Call. play.golang.org/p/ssIXKO-RKB Commented Dec 4, 2017 at 8:47
  • Isn't that why we use reflection to figure it out? Ok it will be a slice of values. Arguments could be strings, numbers, boolean etc. Usually just those. Commented Dec 4, 2017 at 21:03
  • (b). I've updated the question to hopefully make it clear. codec.NewDecoder is from github.com/ugorji/go/codec Commented Dec 4, 2017 at 21:44

2 Answers 2

2

Try the following.

The basic idea is to create a []reflect.Value for the call to reflect.Call by looping over the function arguments. For each argument, convert the incoming argument type to the type expected in the function call.

var req = struct {
    Ver      int
    MsgID    int
    FuncName string
    Args     interface{}
}{
    Args: []interface{}{[]byte("John"), "Jane", 123, "456"},
}
args := req.Args.([]interface{})

var funcs Funcs
f := reflect.ValueOf(funcs).MethodByName("Hello")
var callArgs []reflect.Value
t := f.Type()
// For each function argument ...
for i := 0; i < t.NumIn(); i++ {
    t := t.In(i)
    v := reflect.New(t).Elem()
    if i < len(args) {
        // Convert arg to type of v and set.
        arg := args[i]
        switch t.Kind() {
        case reflect.String:
            switch arg := arg.(type) {
            case string:
                v.SetString(arg)
            case []byte:
                v.SetString(string(arg))
            default:
                panic("not supported")
            }
        case reflect.Slice:
            if t.Elem() != reflect.TypeOf(byte(0)) {
                panic("not supported")
            }
            switch arg := arg.(type) {
            case string:
                v.SetBytes([]byte(arg))
            case []byte:
                v.SetBytes(arg)
            default:
                panic("not supported")
            }

        case reflect.Int:
            switch arg := arg.(type) {
            case int:
                v.SetInt(int64(arg))
            case string:
                i, err := strconv.ParseInt(arg, 10, 0)
                if err != nil {
                    panic("bad int")
                }
                v.SetInt(i)
            default:
                panic("not supported")
            }
        default:
            panic("not supported")
        }
    }
    // Collect arguments for the call below.
    callArgs = append(callArgs, v)
}

result := f.Call(callArgs)
fmt.Println(result)

The snippet includes conversions for string, []byte and int types. Conversions for other types can be added.

playground example

Sign up to request clarification or add additional context in comments.

2 Comments

This doesn't work for me as it stands. Args is of type interface{}. See my updated answer in my attempt at solving it.
With a bit of tweaking I got this to work. It was pretty close and just needed other types added.
1

I was wrong - problem is using [] instead of singular value:

  val := reflect.ValueOf(args)

  //  Do some kind of conversion...
  a := []reflect.Value{val}

  result := f.Call(a) 

https://play.golang.org/p/AqQama-LJv

1 Comment

that doesn't work. Recovered in f reflect: Call using []interface {} as type string. At the top of the question I mention that args is an interface{}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.