如何在Go中将动态创建的结构体作为非指针对象传递

huangapple go评论90阅读模式
英文:

How to pass a dynamically created struct as non-pointer object in Go

问题

我试图动态地将参数传递给URL路由处理函数。我认为可以使用反射包将URL中的值映射到具有一个参数的函数,该参数恰好是一个匿名结构体。我已经创建了要传递给处理程序函数的结构体,但最终它变成了指向结构体的指针。如果我将处理程序函数的签名更改为期望一个指针,那么创建的结构体最终会变成指向指针的指针,我想。无论如何,以下是代码(紧随其后的是恐慌):

英文:

I'm trying to dynamically pass parameters to a URL route handler function. I thought it would be possible to use the reflection package to convert a map of values from the URL to the function that has one parameter that happens to be an anonymous struct. I've gotten as far as creating the struct to pass to the handler function but it ends up being a pointer to the struct. If I change the handler function's signature to expect a pointer the created struct ends up being a pointer to a pointer, I think. At any rate, here's the code (the panic follows):

Link: http://play.golang.org/p/vt_wNY1f08

package main

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"github.com/gorilla/mux"
)


func mapToStruct(obj interface{}, mapping map[string]string) error {
	dataStruct := reflect.Indirect(reflect.ValueOf(obj))
	
	if dataStruct.Kind() != reflect.Struct {
		return errors.New("expected a pointer to a struct")
	}
	
	for key, data := range mapping {
		structField := dataStruct.FieldByName(key)
		
		if !structField.CanSet() {
			fmt.Println("Can't set")
			continue
		}

		var v interface{}

		switch structField.Type().Kind() {
		case reflect.Slice:
			v = data
		case reflect.String:
			v = string(data)
		case reflect.Bool:
			v = string(data) == "1"
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
			x, err := strconv.Atoi(string(data))
			if err != nil {
				return errors.New("arg " + key + " as int: " + err.Error())
			}
			v = x
		case reflect.Int64:
			x, err := strconv.ParseInt(string(data), 10, 64)
			if err != nil {
				return errors.New("arg " + key + " as int: " + err.Error())
			}
			v = x
		case reflect.Float32, reflect.Float64:
			x, err := strconv.ParseFloat(string(data), 64)
			if err != nil {
				return errors.New("arg " + key + " as float64: " + err.Error())
			}
			v = x
		case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			x, err := strconv.ParseUint(string(data), 10, 64)
			if err != nil {
				return errors.New("arg " + key + " as int: " + err.Error())
			}
			v = x
		default:
			return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
		}

		structField.Set(reflect.ValueOf(v))
	}
	return nil
}

type RouteHandler struct {
	Handler interface{}
}

func (h RouteHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	t := reflect.TypeOf(h.Handler)
	
	handlerArgs := reflect.New(t.In(0)).Interface()
	
	if err := mapToStruct(handlerArgs, mux.Vars(req)); err != nil {
		panic(fmt.Sprintf("Error converting params"))
	}
	
	f := reflect.ValueOf(h.Handler)

	args := []reflect.Value{reflect.ValueOf(handlerArgs)}
	f.Call(args)
	
	fmt.Fprint(w, "Hello World")
}


type App struct {
	Router mux.Router
}

func (app *App) Run(bind string, port int) {
	bind_to := fmt.Sprintf("%s:%d", bind, port)
	http.Handle("/", &app.Router)
	http.ListenAndServe(bind_to, &app.Router)
}

func (app *App) Route(pat string, h interface{}) {
	app.Router.Handle(pat, RouteHandler{Handler:h})
}


func home(args struct{Category string}) {
	fmt.Println("home", args.Category)	
}


func main() {
	app := &App{}
	app.Route("/products/{Category}", home)
	app.Run("0.0.0.0", 8080)
}

Panic:

2013/03/28 18:48:43 http: panic serving 127.0.0.1:51204: reflect: Call using *struct { Category string } as type struct { Category string }
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:589 (0x3fb66)
    _func_004: buf.Write(debug.Stack())
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:1443 (0x11cdb)
    panic: reflect·call(d->fn, d->args, d->siz);
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:428 (0x484ba)
    Value.call: panic("reflect: " + method + " using " + xt.String() + " as type " + targ.String())
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:334 (0x47c3a)
    Value.Call: return v.call("Call", in)
/Users/matt/Workspaces/Go/src/pants/pants.go:86 (0x2f36)
    RouteHandler.ServeHTTP: f.Call(args)
/Users/matt/Workspaces/Go/src/pants/pants.go:1 (0x347c)
    (*RouteHandler).ServeHTTP: package main
/Users/matt/Workspaces/Go/src/github.com/gorilla/mux/mux.go:86 (0x5a699)
    com/gorilla/mux.(*Router).ServeHTTP: handler.ServeHTTP(w, req)
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:669 (0x337b6)
    (*conn).serve: handler.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:271 (0xfde1)
    goexit: runtime·goexit(void)

答案1

得分: 3

在你的reflect.Value对象上调用Elem()。

引用自《反射定律》文章:

> 要获取p指向的内容,我们调用Value的Elem方法,该方法通过指针进行间接引用。

英文:

Call Elem() on your reflect.Value object.

Quoting from The Laws of Reflection article:

> To get to what p points to, we call the Elem method of Value, which indirects through the pointer

答案2

得分: 0

请注意,reflect.New() 创建的是一个指向您传递的类型的值的指针。因此,在下一行中:

handlerArgs := reflect.New(t.In(0)).Interface()

handlerArgs 将是一个指向类型为 t.In(0) 的结构体的指针。您需要解引用该指针,以获得适合传递给函数的值。

我建议以下操作:

  1. handlerArgs 声明为 *reflect.Value,以获取实际的结构体值:

     handlerArgs := reflect.New(t.In(0)).Elem()
    
  2. mapToStruct 接受这样的 *reflect.Value,而不是包含指向结构体的指针的 interface{}(毕竟它是您反射代码的辅助函数)。

  3. Call() 调用中直接使用 handlerArgs 作为函数参数之一。

英文:

Note that reflect.New() creates a pointer to a value of the type you pass it. So on the following line:

handlerArgs := reflect.New(t.In(0)).Interface()

handlerArgs will be a pointer to a struct of type t.In(0). You will need to dereference that pointer to have a value suitable to pass to the function.

I'd suggest the following:

  1. Make hanlerArgs a *reflect.Value for the actual struct value:

     handlerArgs := reflect.New(t.In(0)).Elem()
    
  2. Make mapToStruct take such a *reflect.Value instead of an interface{} holding a pointer to a struct (it is a helper for your reflection code, after all).

  3. Use handlerArgs directly as one of the function arguments for the `Call() invocation.

huangapple
  • 本文由 发表于 2013年3月29日 06:55:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/15693590.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定