Golang动态变量引用

9
在Go语言中,我希望能像这样做一些事情。我有一个包含许多结构体的大型对象(使用Google的protobuf)。下面是一个人为制造的例子:
person.name = "testing"
person.address.street = "123 test st"
person.address.city = "tester"
person.address.zip = 90210
person.billing.address.same = true

我希望能够动态地引用事物。例如:

key := "person.address.zip"
fmt.Println("the value of key: " + key) // would like to get 90210
key := "person.address.city"
fmt.Println("the value of key: " + key) // would like to get "tester"

在Go语言中可以实现这个功能吗?如果可以,我该如何实现?我正在创建一个报告,其中只包含对象的子集,并且我希望能够创建一个映射文件,让用户将键/值相互映射,然后我的程序将输出相应的值。我已经使用Python实现了这个功能,但想尝试使用Go :)


是的,这是可能的。它为您提供了包反射。反射的缺点通常是工作量很大。另一方面 - 没有反射就无法编写像html/template或text/template这样完美的包,其中编写风格类似于解释语言(如Python),尽管Go是编译的。 - lofcek
反射是缓慢的,应该作为最后的手段。 - OneOfOne
4
如果你真的需要类似这样的东西,你应该使用(嵌套)映射而不是结构值的层次结构。 - kostix
嵌套映射(如map[string]interface{})是你的好朋友。 - openwonk
2个回答

8
您可以使用reflect包中的func (v Value) FieldByName(name string) Value方法:

FieldByName方法返回具有给定名称的结构体字段。如果未找到字段,则返回零值。如果v的Kind不是struct,则会抛出异常。

以下是示例代码:
package main

import "fmt"
import "reflect"

func main() {
    person := Person{}
    person.name = "testing"
    person.address.street = "123 test st"
    person.address.city = "tester"
    person.address.zip = 90210
    person.billing.address.same = true

    v := reflect.ValueOf(person)
    f := v.FieldByName("address")
    key := f.FieldByName("zip")
    fmt.Println(key)                   // 90210
    fmt.Println(f.FieldByName("city")) // tester    
}

type Person struct {
    name    string
    address Address
    billing Billing
}
type Billing struct {
    address Address
}
type Address struct {
    street, city string
    zip          int
    same         bool
}

输出:

90210
tester

对于你的特殊情况,你可以使用fmt.Println(field(person, "person.address.zip")),就像这个工作示例代码(仅供演示):
package main

import "fmt"
import "reflect"
import "strings"

func field(t interface{}, key string) reflect.Value {
    strs := strings.Split(key, ".")
    v := reflect.ValueOf(t)
    for _, s := range strs[1:] {
        v = v.FieldByName(s)
    }
    return v
}
func main() {
    person := Person{}
    person.name = "testing"
    person.address.street = "123 test st"
    person.address.city = "tester"
    person.address.zip = 90210
    person.billing.address.same = true

    fmt.Println(field(person, "person.address.zip"))  //90210
    fmt.Println(field(person, "person.address.city")) //tester
}

type Person struct {
    name    string
    address Address
    billing Billing
}
type Billing struct {
    address Address
}
type Address struct {
    street, city string
    zip          int
    same         bool
}

输出:

90210
tester

1

我不熟悉protobuf的内部结构,也不知道它是否提供任何方法来做到这一点。

但是,(1)如果您想以您描述的方式读取值 - 通过动态链接字段,(2)并且您想多次读取它; 我会将其序列化为json,并使用this包。它非常快速,并且给您(几乎)所需的相同语义:

// assuming your object got marshaled to this for example
json := `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
value := gjson.Get(json, "name.last")
println(value.String())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接