Itere pelos campos de uma estrutura no Go

107

Basicamente, a única maneira (que eu conheço) de iterar por meio dos valores dos campos de a structé assim:

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
}

Eu estava me perguntando se há uma maneira melhor e mais versátil de fazer []interface{}{ r.a_number, r.a_string, }isso, então não preciso listar cada parâmetro individualmente ou, alternativamente, há uma maneira melhor de fazer um loop em uma estrutura?

Tentei olhar o reflectpacote, mas bati em uma parede, porque não tenho certeza do que fazer quando o recuperar reflect.ValueOf(*r).Field(0).

Obrigado!

oninonsense
fonte
5
Aqui está um artigo muito interessante sobre reflexão: blog.golang.org/laws-of-reflection Seguindo um dos exemplos do artigo: play.golang.org/p/_bKAQ3dQlu Observe, no entanto, que você não pode pesquisar campos não exportados com o pacote de reflexão (ou seja, campos que começam com minúsculas)
início de

Respostas:

126

Depois de recuperar o reflect.Valuedo campo usando, Field(i)você pode obter um valor de interface dele chamando Interface(). O referido valor da interface representa então o valor do campo.

Não há função para converter o valor do campo em um tipo concreto, pois não há, como você deve saber, genéricos em uso. Assim, não há nenhuma função com a assinatura GetValue() T em Tser o tipo daquele campo (que muda de curso, dependendo do campo).

O mais próximo que você pode alcançar em go é GetValue() interface{}e isso é exatamente o que reflect.Value.Interface() oferece.

O código a seguir ilustra como obter os valores de cada campo exportado em uma estrutura usando reflexão ( reprodução ):

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)
}
nemo
fonte
24
Sim, porque go não precisa de genéricos. Tosse, tosse :-) Existe uma maneira de obter o tipo do campo?
U Avalos
1
via reflect.Value.Type(), sim. Mas observe que os tipos não são cidadãos de primeira classe em go, portanto, você só pode instanciar novos valores desse tipo usando reflect.
nemo
6
v.Field(i).Interface()entra em pânico se você tentar acessar campos privados não exportados. Basta ter cuidado :)
Tarion
10
Usando v.Field(i).CanInterface() um pode evitar o pânico em caso de campos não exportados.
Pedram Esmaeeli
1
Como posso obter o nome do campo?
Sathesh de
33

Se você deseja iterar os campos e valores de uma estrutura, pode usar o código Go abaixo como referência.

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())
    }
}

Corra no playground

Nota: Se os campos em sua estrutura não forem exportados, o v.Field(i).Interface()dará pânicopanic: reflect.Value.Interface: cannot return value obtained from unexported field or method.

Chetan Kumar
fonte
0

Tomando a solução Chetan Kumar e caso você precise se inscrever em ummap[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"))
}


AÇO
fonte