如何在Golang中从reflect.Value获取底层值?

45

我找到了一些关于Go(golang)反射的代码,但我在获取基础值方面遇到了麻烦,以便我可以从结构体及其字段创建一个map[string]string

最终,我希望将结果转换为map[string]interface{},但这个问题有点儿阻塞我。

目前我所拥有的代码:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    FirstName string `tag_name:"tag 1"`
    LastName  string `tag_name:"tag 2"`
    Age       int  `tag_name:"tag 3"`
}

func inspect(f interface{}) map[string]string {

    m := make(map[string]string)
    val := reflect.ValueOf(f).Elem()

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)

        f := valueField.Interface()
        val := reflect.ValueOf(f)
        m[typeField.Name] = val.String()
    }

    return m
}

func dump(m map[string]string) {

    for k, v := range m {
        fmt.Printf("%s : %s\n", k, v)
    }
}

func main() {
    f := &Foo{
        FirstName: "Drew",
        LastName:  "Olson",
        Age:       30,
    }

    a := inspect(f)

    dump(a)
}

运行代码后的输出:

FirstName : Drew
LastName : Olson
Age : <int Value>

据我所了解,FirstName和LastName的输出是实际的reflect.Value对象,但对于字符串,value上的String()方法只会输出底层字符串。 我想要将int获取并转换为字符串,但是从反射包文档中我没有立即看到如何实现。

那么...我该如何在Go语言中获取reflect.Value的底层值?


1
一个 reflect.Value 的 printf 将会(在 Go 1.5 中)打印它的实际值。请参见下面的我的回答 - VonC
@VonC 很酷,感谢您的帮助,最终在您建议使用 fmt 的帮助下成功获取了该值:https://goplay.space/#w98PfGoZHl8 - Macilias
运行解决方案,包括字段名称、类型、值和标签:https://goplay.space/#15XAsJBH5Sb - Macilias
@Macilias 做得好。 (顺便说一句,我不知道 goplay.space,看起来很方便) - VonC
4个回答

30
一个很好的解析值的例子是fmt包。请参见this code
使用上述代码来匹配您的问题,看起来像这样:
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    m[typeField.Name] = strconv.FormatInt(val.Int(), 10)
case reflect.String:
    m[typeField.Name] = val.String()    
// etc...
}

基本上,您需要检查所有可用种类


1
是的 - 我本来要在这里发布我的答案,基本上是一样的。我想我尝试过很多次,但没有包括 reflect.Int 类型,认为 reflect.Int32 将覆盖它。不过,一旦我添加了那个情况,事情就变得更好了。 - lucidquiet
很好你提到了这个。原因是Int类型是平台相关的(但至少有32位宽),而Int32Int64具有固定的大小。 - nemo

6
看起来你已经走上了正确的轨道。我看到你代码中存在一个问题,即它对值做出了一些假设,也就是说你何时调用Elem()以及调用多少次(以解决指针)等方面存在问题。为了知道这一点,你需要查看reflect.Kind。值是否是reflect.Ptr?然后使用Elem()
一旦你从val.Interface() / val.String() / val.Int()获取了值,则可以根据需要转换你的值。你使用的内容将取决于reflect.Kind。要将int转换为/从string转换,你需要使用strconv包。 encoding/jsonencoding/xml包已经完成了这种工作。源代码提供了一些很好的示例。例如,请查看encoding/xml/read.go中的copyValueencoding/xml/marshal.go中的marshalSimple

5
这应该在 Go 1.5(2015年8月)更容易实现。 请参阅 review 8731commit 049b89d,由 Rob Pike (robpike) 提交的内容:

fmt: 特别对待 reflect.Value - 作为它所持有的值

这将允许您打印出 Reflect.Value() 参数的实际值:

当将reflect.Value传递给Printf(等)时,fmt调用了String方法,该方法不会公开其内容。为了获取内容,可以调用Value.Interface(),但如果Value未公开或受到其他限制,则此操作是非法的。
这个CL通过对fmt包进行微小的更改来改善这种情况:当我们将一个reflect.Value作为参数时,我们将其视为在包内部创建的reflect.Value一样对待。这意味着我们始终打印Value的内容,就好像那是Printf的参数一样。
这可能是一个破坏性的变化,但我认为这是一个真正的改进,而且与我们对此包中格式化输出所做的许多其他调整一样,并没有更大的破坏。

2
另一个简单的解决方案可以是:
flavorName = fmt.Sprintf("%v",strct)

"fmt.Sprintf()"会返回一个可以存储在变量中的值。

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