在Go中遍历结构体的字段

191

基本上,我所知道的迭代struct字段值的唯一方法是这样的:

type Example struct {
    a_number uint32
    a_string string
}

//...

r := &Example{(2 << 31) - 1, "...."}:
for _, d:= range []interface{}{ r.a_number, r.a_string, } {
  //do something with the d
}

我想知道是否有更好、更灵活的方法来实现[]interface{}{ r.a_number, r.a_string, },这样我就不需要逐个列出每个参数;或者,有没有更好的方法来遍历一个结构体?

我尝试查阅reflect包,但我遇到了困难,因为我不确定一旦我检索到reflect.ValueOf(*r).Field(0)后该怎么办。

谢谢!


7
这是一篇关于反射的非常有趣的文章:http://blog.golang.org/laws-of-reflection。以下是文章中的一个例子: http://play.golang.org/p/_bKAQ3dQlu。但请注意,您无法使用反射包查找非公开字段(即以小写字母开头的字段)。 - creack
8个回答

184
在使用Field(i)方法检索字段的reflect.Value之后,您可以通过调用Interface()方法从中获取接口值。该接口值表示字段的值。
由于go语言中没有泛型,因此没有将字段值转换为具体类型的函数。因此,没有带有T参数的GetValue() T函数(当然,这个类型取决于字段)。在go语言中最接近的方法是GetValue() interface{},而这正是reflect.Value.Interface()提供的功能。
以下代码演示了如何使用反射获得结构体中每个公开字段的值(play):
import (
    "fmt"
    "reflect"
)

func main() {
    x := struct{Foo string; Bar int }{"foo", 2}

    v := reflect.ValueOf(x)

    values := make([]interface{}, v.NumField())

    for i := 0; i < v.NumField(); i++ {
        values[i] = v.Field(i).Interface()
    }

    fmt.Println(values)
}

39
是的,因为Go语言不需要泛型。咳咳 :-) 有办法获得字段的类型吗? - U Avalos
1
通过 reflect.Value.Type(),是可以的。但请注意,在 Go 中,类型不是一等公民,因此您只能使用 reflect 实例化该类型的新值。 - nemo
9
如果您尝试访问未公开的私有字段,v.Field(i).Interface() 会导致崩溃。只需小心即可 :) - Tarion
12
使用 v.Field(i).CanInterface() 可以避免在访问未导出字段时引发 panic。 - Pedram Esmaeeli
3
我该如何获取字段名称? - Sathesh
显示剩余6条评论

109

如果您想迭代结构体的字段和值,则可以使用下面的Go代码作为参考。

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Fname  string
    Lname  string
    City   string
    Mobile int64
}

func main() {
    s := Student{"Chetan", "Kumar", "Bangalore", 7777777777}
    v := reflect.ValueOf(s)
    typeOfS := v.Type()

    for i := 0; i< v.NumField(); i++ {
        fmt.Printf("Field: %s\tValue: %v\n", typeOfS.Field(i).Name, v.Field(i).Interface())
    }
}

playground中运行

注意:如果您的结构体中的字段未导出,则v.Field(i).Interface()会产生恐慌:panic: reflect.Value.Interface: cannot return value obtained from unexported field or method.


1
这是一个很棒的答案。 - ion
1
这就是我一直在寻找的答案。 - Yehuda Makarov
1
谢谢,这太完美了! - Don
1
太棒了!像魔法一样运行良好。 - Rewanth Tammana

37

在2021年第三季度,Go 1.17应该添加一个新选项,通过提交009bfeaCL 281233来修复问题42782

反射:添加VisibleFields函数

在编写代码反射struct类型时,通常需要了解完整的struct字段集,包括由于匿名成员嵌入而可用的字段,同时排除被擦除的字段,因为它们与另一个同名字段处于相同级别。

这个逻辑并不是很复杂,但有点微妙且容易出错。

该 CL 添加了一个新的reflect.VisibleFields()函数到reflect包中,返回适用于给定struct类型的完整有效字段集。

fields := reflect.VisibleFields(typ)
for j, field := range fields {
    ...
}

例子,

type employeeDetails struct {
    id          int16
    name        string
    designation string
}
func structIterator() {
    fields := reflect.VisibleFields(reflect.TypeOf(struct{ employeeDetails }{}))
    for _, field := range fields {
        fmt.Printf("Key: %s\tType: %s\n", field.Name, field.Type)
    }
}

我以为它已经包含在1.17中了。谢谢分享。 - arif
@arif 确实是:https://golang.org/doc/go1.17#reflect - VonC
奇怪。我刚刚下载了1.17版本并尝试使用它,但它找不到该函数。也许我漏掉了一些愚蠢的东西。 - arif
我只需要使用 reflect.VisibleFields。我试图进行编辑,但似乎队列已满。 - arif
@arif 很好的观点。我已经相应地编辑/修复了答案。 - VonC
1
@arif 谢谢你的示例。 - VonC

7
也许太晚了 :))) 但还有另一个解决方案,您可以找到结构体的键和值并对其进行迭代。
package main

import (
    "fmt"
    "reflect"
)

type person struct {
    firsName string
    lastName string
    iceCream []string
}

func main() {
    u := struct {
        myMap    map[int]int
        mySlice  []string
        myPerson person
    }{
        myMap:   map[int]int{1: 10, 2: 20},
        mySlice: []string{"red", "green"},
        myPerson: person{
            firsName: "Esmaeil",
            lastName: "Abedi",
            iceCream: []string{"Vanilla", "chocolate"},
        },
    }
    v := reflect.ValueOf(u)
    for i := 0; i < v.NumField(); i++ {
        fmt.Println(v.Type().Field(i).Name)
        fmt.Println("\t", v.Field(i))
    }
}

and there is no *panic* for v.Field(i)

3
你好,欢迎来到Stack Overflow!请参观一下 tour。谢谢你对问题做出回答(问题的年龄不重要)。能否再详细解释一下你的回答如何解决这个问题? - Jeanne Dark

0

使用这个:

type x struct {
    Id  int
    jsj int
}
func main() {
    x2 := x{jsj: 10, Id: 5}
    v := reflect.ValueOf(x2)
    for i := 0; i < v.NumField(); i++ {
        fmt.Println(v.Field(i))
    }
}

====>10

====>5


-1

参考Chetan Kumar的解决方案,如果你需要应用到map[string]int中:

package main

import (
    "fmt"
    "reflect"
)

type BaseStats struct {
    Hp           int
    HpMax        int
    Mp           int
    MpMax        int
    Strength     int
    Speed        int
    Intelligence int
}

type Stats struct {
    Base map[string]int
    Modifiers []string
}

func StatsCreate(stats BaseStats) Stats {
    s := Stats{
        Base: make(map[string]int),
    }

    //Iterate through the fields of a struct
    v := reflect.ValueOf(stats)
    typeOfS := v.Type()

    for i := 0; i< v.NumField(); i++ {
        val := v.Field(i).Interface().(int)
        s.Base[typeOfS.Field(i).Name] = val
    }
    return s
}

func (s Stats) GetBaseStat(id string) int {
    return s.Base[id]
}


func main() {
    m := StatsCreate(BaseStats{300, 300, 300, 300, 10, 10, 10})

    fmt.Println(m.GetBaseStat("Hp"))
}



-1

使用reflect包。首先,用reflect.TypeOf获取变量的类型,并使用reflect.NumField获取元素数量。为了迭代地获得结构体字段的值,必须反射该变量并使用函数rg.Elem().Field(i)

package main

import (
    "fmt"
    "reflect"
)

type Gopher struct {
    Name  string
    Color string
    Year  int
}

func main() {
    g := Gopher{Name: "AAA", Color: "BBBB", Year: 2021}

    gtype := reflect.TypeOf(g)

    numFields := gtype.NumField()

    rg := reflect.ValueOf(&g)

    for i := 0; i < numFields; i++ {
        fmt.Println(rg.Elem().Field(i))
    }
}

-2
在Go语言中,您可以使用反射包(reflect package)来遍历结构体(struct)的字段。反射包允许您在运行时检查值的属性,包括它们的类型和值。以下是一个遍历结构体字段的示例:

Go Playground

package main

import (
    "fmt"
    "reflect"
)

type Movie struct {
    Name string
    Year int
}

func main() {
    p := Movie{"The Dark Knight", 2008}

    val := reflect.ValueOf(p)
    typ := val.Type()

    for i := 0; i < val.NumField(); i++ {
        field := val.Field(i)
        fieldType := typ.Field(i)

        fmt.Printf("Field Name: %s, Field Value: %v\n", fieldType.Name, field.Interface())
    }
}

输出:

Field Name: Name, Field Value: The Dark Knight
Field Name: Age, Field Value: 2008

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