反射:设置指针的字段

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

Reflect: setting a field of a pointer

问题

我正在尝试做类似这样的事情:

定义带有名为env的标签的结构体:

  1. type Env struct {
  2. Port string `env:"PORT"`
  3. }

调用某个函数,该函数将使用os.Getenv获取环境变量名称,并将其设置到结构体中。

目前,我有以下代码:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "reflect"
  6. )
  7. func ParseEnv(t interface{}, v interface{}) {
  8. it := reflect.TypeOf(t)
  9. for i := 0; i < it.NumField(); i++ {
  10. field := it.Field(i)
  11. value := os.Getenv(field.Tag.Get("env"))
  12. if value == "" {
  13. continue
  14. }
  15. reflect.ValueOf(v).Elem().FieldByName(field.Name).SetString(value)
  16. }
  17. }
  18. type Env struct {
  19. Port string `env:"PORT"`
  20. DatabaseURL string `env:"DATABASE_URL"`
  21. }
  22. func main() {
  23. os.Setenv("PORT", "8080")
  24. os.Setenv("DATABASE_URL", "postgres://user:pass@host:5432/my-db")
  25. env := Env{}
  26. ParseEnv(env, &env)
  27. fmt.Println(env)
  28. }

但是,正如你所看到的,我必须同时传递引用和指针给我的函数。

虽然这个方法可以工作,但是我觉得它很丑陋。

如果我只尝试传递指针,我无法得到正确的类型(因为它将是*interface{}),如果我只传递引用,我无法使用reflect设置值(即使我可以,它也不会起作用)。

有没有一种更合理的方法来做到这一点?

英文:

I'm trying to do something like this:

Define structs with tags named env:

  1. type Env struct {
  2. Port string `env:&quot;PORT&quot;`
  3. }

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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;os&quot;
  5. &quot;reflect&quot;
  6. )
  7. func ParseEnv(t interface{}, v interface{}) {
  8. it := reflect.TypeOf(t)
  9. for i := 0; i &lt; it.NumField(); i++ {
  10. field := it.Field(i)
  11. value := os.Getenv(field.Tag.Get(&quot;env&quot;))
  12. if value == &quot;&quot; {
  13. continue
  14. }
  15. reflect.ValueOf(v).Elem().FieldByName(field.Name).SetString(value)
  16. }
  17. }
  18. type Env struct {
  19. Port string `env:&quot;PORT&quot;`
  20. DatabaseURL string `env:&quot;DATABASE_URL&quot;`
  21. }
  22. func main() {
  23. os.Setenv(&quot;PORT&quot;, &quot;8080&quot;)
  24. os.Setenv(&quot;DATABASE_URL&quot;, &quot;postgres://user:pass@host:5432/my-db&quot;)
  25. env := Env{}
  26. ParseEnv(env, &amp;env)
  27. fmt.Println(env)
  28. }

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

以下是一种更合理的实现你想要的功能的方式。你会注意到,我们只需要传递一个指向结构体的指针,而不是传递两个结构体的副本。

  1. func ParseEnv(val interface{}) {
  2. ptrRef := reflect.ValueOf(val)
  3. if ptrRef.Kind() != reflect.Ptr {
  4. panic("pointer to struct expected")
  5. }
  6. ref := ptrRef.Elem()
  7. if ref.Kind() != reflect.Struct {
  8. panic("pointer to struct expected")
  9. }
  10. refType := ref.Type()
  11. for i := 0; i < refType.NumField(); i++ {
  12. field := refType.Field(i)
  13. value := os.Getenv(field.Tag.Get("env"))
  14. if value == "" {
  15. continue
  16. }
  17. ref.Field(i).SetString(value)
  18. }
  19. }

以上函数应该以以下方式调用:

  1. 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.

  1. func ParseEnv(val interface{}) {
  2. ptrRef := reflect.ValueOf(val)
  3. if ptrRef.Kind() != reflect.Ptr {
  4. panic(&quot;pointer to struct expected&quot;)
  5. }
  6. ref := ptrRef.Elem()
  7. if ref.Kind() != reflect.Struct {
  8. panic(&quot;pointer to struct expected&quot;)
  9. }
  10. refType := ref.Type()
  11. for i := 0; i &lt; refType.NumField(); i++ {
  12. field := refType.Field(i)
  13. value := os.Getenv(field.Tag.Get(&quot;env&quot;))
  14. if value == &quot;&quot; {
  15. continue
  16. }
  17. ref.Field(i).SetString(value)
  18. }
  19. }

The above function should be invoked in the following way:

  1. ParseEnv(&amp;env)

Example: https://play.golang.org/p/_BwWz2oUql

huangapple
  • 本文由 发表于 2015年7月29日 10:16:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/31690160.html
匿名

发表评论

匿名网友

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

确定