英文:
Reflect: setting a field of a pointer
问题
我正在尝试做类似这样的事情:
定义带有名为env的标签的结构体:
type Env struct {
    Port string `env:"PORT"`
}
调用某个函数,该函数将使用os.Getenv获取环境变量名称,并将其设置到结构体中。
目前,我有以下代码:
package main
import (
    "fmt"
    "os"
    "reflect"
)
func ParseEnv(t interface{}, v interface{}) {
    it := reflect.TypeOf(t)
    for i := 0; i < it.NumField(); i++ {
        field := it.Field(i)
        value := os.Getenv(field.Tag.Get("env"))
        if value == "" {
            continue
        }
        reflect.ValueOf(v).Elem().FieldByName(field.Name).SetString(value)
    }
}
type Env struct {
    Port        string `env:"PORT"`
    DatabaseURL string `env:"DATABASE_URL"`
}
func main() {
    os.Setenv("PORT", "8080")
    os.Setenv("DATABASE_URL", "postgres://user:pass@host:5432/my-db")
    env := Env{}
    ParseEnv(env, &env)
    fmt.Println(env)
}
但是,正如你所看到的,我必须同时传递引用和指针给我的函数。
虽然这个方法可以工作,但是我觉得它很丑陋。
如果我只尝试传递指针,我无法得到正确的类型(因为它将是*interface{}),如果我只传递引用,我无法使用reflect设置值(即使我可以,它也不会起作用)。
有没有一种更合理的方法来做到这一点?
英文:
I'm trying to do something like this:
Define structs with tags named env:
type Env struct {
     Port string `env:"PORT"`
}
Call some function which will get the environment variable names using os.Getenv and put set it in the struct.
Right now, I have this:
package main
import (
	"fmt"
	"os"
	"reflect"
)
func ParseEnv(t interface{}, v interface{}) {
	it := reflect.TypeOf(t)
	for i := 0; i < it.NumField(); i++ {
		field := it.Field(i)
		value := os.Getenv(field.Tag.Get("env"))
		if value == "" {
			continue
		}
		reflect.ValueOf(v).Elem().FieldByName(field.Name).SetString(value)
	}
}
type Env struct {
	Port        string `env:"PORT"`
	DatabaseURL string `env:"DATABASE_URL"`
}
func main() {
	os.Setenv("PORT", "8080")
	os.Setenv("DATABASE_URL", "postgres://user:pass@host:5432/my-db")
	env := Env{}
	ParseEnv(env, &env)
	fmt.Println(env)
}
http://play.golang.org/p/b8uPPVo4aV
But, as you can see, I have to pass both the reference and the pointer to my function.
While this works, it is very ugly (at least I think it is).
If I try to pass the pointer only, I can't get the type right (because it will be an *interface{}) and, if I pass only the reference, I can't set the values using reflect (even if I could, it would not work).
Is there a sane way of doing this?
答案1
得分: 4
以下是一种更合理的实现你想要的功能的方式。你会注意到,我们只需要传递一个指向结构体的指针,而不是传递两个结构体的副本。
func ParseEnv(val interface{}) {
    ptrRef := reflect.ValueOf(val)
    if ptrRef.Kind() != reflect.Ptr {
        panic("pointer to struct expected")
    }
    ref := ptrRef.Elem()
    if ref.Kind() != reflect.Struct {
        panic("pointer to struct expected")
    }
    refType := ref.Type()
    for i := 0; i < refType.NumField(); i++ {
        field := refType.Field(i)
        value := os.Getenv(field.Tag.Get("env"))
        if value == "" {
            continue
        }
        ref.Field(i).SetString(value)
    }
}
以上函数应该以以下方式调用:
ParseEnv(&env)
示例:https://play.golang.org/p/_BwWz2oUql
英文:
Below is a "saner" way of doing what you want. You will notice that, instead of passing in two copies of the struct, we only need a pointer to the struct.
func ParseEnv(val interface{}) {
	ptrRef := reflect.ValueOf(val)
	if ptrRef.Kind() != reflect.Ptr {
		panic("pointer to struct expected")
	}
	ref := ptrRef.Elem()
	if ref.Kind() != reflect.Struct {
		panic("pointer to struct expected")
	}
	refType := ref.Type()
	for i := 0; i < refType.NumField(); i++ {
		field := refType.Field(i)
		value := os.Getenv(field.Tag.Get("env"))
		if value == "" {
			continue
		}
		ref.Field(i).SetString(value)
	}
}
The above function should be invoked in the following way:
ParseEnv(&env)
Example: https://play.golang.org/p/_BwWz2oUql
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论