Iterar a través de los campos de una estructura en Go

107

Básicamente, la única forma (que yo sepa) de iterar a través de los valores de los campos de a structes así:

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
}

Me preguntaba si hay una forma mejor y más versátil de lograrlo []interface{}{ r.a_number, r.a_string, }, por lo que no necesito enumerar cada parámetro individualmente o, alternativamente, ¿hay una mejor manera de recorrer una estructura?

Traté de mirar a través del reflectpaquete, pero choqué contra una pared, porque no estoy seguro de qué hacer una vez que lo recupere reflect.ValueOf(*r).Field(0).

¡Gracias!

omninonsense
fuente
5
Aquí hay un artículo muy interesante sobre la reflexión: blog.golang.org/laws-of-reflection Siguiendo uno de los ejemplos del artículo: play.golang.org/p/_bKAQ3dQlu Sin embargo, tenga en cuenta que no puede buscar campos no exportados con el paquete
reflect

Respuestas:

126

Una vez que haya recuperado el valor reflect.Valuedel campo utilizando Field(i), puede obtener un valor de interfaz llamando a Interface(). Entonces, dicho valor de interfaz representa el valor del campo.

No hay ninguna función para convertir el valor del campo a un tipo concreto ya que, como sabrá, no hay genéricos disponibles. Por tanto, no hay función con la firma GetValue() T conT ser el tipo de ese campo (que cambia por supuesto, dependiendo del campo).

Lo más cercano que puede lograr en go es GetValue() interface{}y esto es exactamente lo quereflect.Value.Interface() ofrece.

El siguiente código ilustra cómo obtener los valores de cada campo exportado en una estructura usando la reflexión ( reproducir ):

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
fuente
24
Sí, porque go no necesita genéricos. Tos, tos :-) ¿Hay alguna forma de obtener el tipo de campo?
U Avalos
1
vía reflect.Value.Type(), sí. Pero tenga en cuenta que los tipos no son ciudadanos de primera clase en marcha, por lo que solo puede crear instancias de nuevos valores de ese tipo utilizando reflect.
nemo
6
v.Field(i).Interface()entra en pánico si intenta acceder a campos privados no exportados. Solo ten cuidado :)
Tarion
10
El uso de v.Field(i).CanInterface() uno puede evitar el pánico en caso de campos no exportados.
Pedram Esmaeeli
1
¿Cómo puedo obtener el nombre del campo?
Sábado
33

Si desea iterar a través de los campos y valores de una estructura, puede usar el siguiente código Go como referencia.

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

Correr en el patio de recreo

Nota: Si los campos en su estructura no se exportan, entonces v.Field(i).Interface()dará pánicopanic: reflect.Value.Interface: cannot return value obtained from unexported field or method.

Chetan Kumar
fuente
0

Tomando solución de Chetan Kumar y en caso de que necesite aplicar a unmap[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"))
}

ACERO
fuente