Usando reflect, ¿cómo se establece el valor de un campo de estructura?

106

tener dificultades para trabajar con campos de estructura usando reflect package. en particular, no he descubierto cómo establecer el valor del campo.

escriba t struct {fi int; cadena de fs}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. obteniendo el nombre del campo i - esto parece funcionar

    var field = reflect.TypeOf(r).Field(i).Name

  2. obteniendo el valor del campo i como a) interfaz {}, b) int - esto parece funcionar

    var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

    var i int = int(reflect.ValueOf(r).Field(i).Int())

  3. establecer el valor del campo i - probar uno - pánico

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic : reflect.Value · SetInt usando el valor obtenido usando un campo no exportado

    asumiendo que no le gustaron los nombres de campo "id" y "name", por lo que se renombró a "Id" y "Name"

    a) ¿Es correcta esta suposición?

    b) si es correcto, se cree que no es necesario ya que en el mismo archivo / paquete

  4. establecer el valor del campo i - probar dos (con los nombres de los campos en mayúsculas) - pánico

    reflect.ValueOf(r).Field(i).SetInt( 465 )

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic : reflect.Value · SetInt usando un valor no direccionable


Las instrucciones a continuación de @peterSO son completas y de alta calidad

Cuatro. esto funciona:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

También documenta que los nombres de los campos deben ser exportables (comience con mayúscula)

cc joven
fuente
el ejemplo más cercano que pude encontrar para alguien que usa reflectpara establecer datos fue comments.gmane.org/gmane.comp.lang.go.general/35045 , pero incluso allí solía json.Unmarshalhacer el trabajo sucio real
cc young
(el comentario anterior es obsoleto)
cc young

Respuestas:

156

Go está disponible como código fuente abierto . Una buena forma de aprender sobre la reflexión es ver cómo la utilizan los principales desarrolladores de Go. Por ejemplo, los paquetes Go fmt y json . La documentación del paquete tiene vínculos a los archivos de código fuente bajo el título Archivos del paquete.

El paquete Go json clasifica y deshace JSON desde y hacia estructuras Go.


Aquí hay un ejemplo paso a paso que establece el valor de un structcampo mientras evita cuidadosamente los errores.

El reflectpaquete Go tiene una CanAddrfunción.

func (v Value) CanAddr() bool

CanAddr devuelve verdadero si la dirección del valor se puede obtener con Addr. Estos valores se denominan direccionables. Un valor es direccionable si es un elemento de un segmento, un elemento de una matriz direccionable, un campo de una estructura direccionable o el resultado de desreferenciar un puntero. Si CanAddr devuelve falso, llamar a Addr entrará en pánico.

El reflectpaquete Go tiene una CanSetfunción que, si true, implica que CanAddrtambién lo es true.

func (v Value) CanSet() bool

CanSet devuelve verdadero si se puede cambiar el valor de v. Un valor se puede cambiar solo si es direccionable y no se obtuvo mediante el uso de campos de estructura no exportados. Si CanSet devuelve falso, llamar a Set o cualquier setter específico de tipo (por ejemplo, SetBool, SetInt64) entrará en pánico.

Necesitamos asegurarnos de que podemos salir Setal structcampo. Por ejemplo,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    // N at start
    fmt.Println(n.N)
    // pointer to struct - addressable
    ps := reflect.ValueOf(&n)
    // struct
    s := ps.Elem()
    if s.Kind() == reflect.Struct {
        // exported field
        f := s.FieldByName("N")
        if f.IsValid() {
            // A Value can be changed only if it is 
            // addressable and was not obtained by 
            // the use of unexported struct fields.
            if f.CanSet() {
                // change value of N
                if f.Kind() == reflect.Int {
                    x := int64(7)
                    if !f.OverflowInt(x) {
                        f.SetInt(x)
                    }
                }
            }
        }
    }
    // N at end
    fmt.Println(n.N)
}

Output:
42
7

Si podemos estar seguros de que todas las comprobaciones de errores son innecesarias, el ejemplo se simplifica a,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    fmt.Println(n.N)
    reflect.ValueOf(&n).Elem().FieldByName("N").SetInt(7)
    fmt.Println(n.N)
}
peterSO
fuente
5
¡Rendirse! en algún lugar hay una respuesta, pero cuatro horas de trabajo en el paquete json no me la han dado. En el paquete de reflexión, extraer información es bastante sencillo, pero los datos de configuración requieren algo de magia negra, por lo que me encantaría ver un ejemplo simple en alguna parte.
cc young
2
¡Excepcional! Si alguna vez estás en Tailandia, ¡déjame invitarte a una cerveza o dos o tres! muchas gracias
cc young
5
Gran ejemplo práctico, este artículo lo desmitificó completamente para mí golang.org/doc/articles/laws_of_reflection.html
danmux
Ejemplo impresionante Aquí hay una muestra de patio de recreo del mismo código play.golang.org/p/RK8jR_9rPh
Sarath Sadasivan Pillai
Esto no establece un campo de estructura. Esto establece un campo en un puntero a struct. Obviamente, esto no responde a la pregunta que hizo OP.
wvxvw
13

Esto parece funcionar:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Number int
    Text string
}

func main() {
    foo := Foo{123, "Hello"}

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))

    reflect.ValueOf(&foo).Elem().Field(0).SetInt(321)

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))
}

Huellas dactilares:

123
321
Asgeir
fuente
¡Gracias! ahora que he leído las notas de PeterSO, esto tiene mucho sentido. Estaba usando foo, no & foo, por lo que no se podía cambiar y no estaba seguro de qué se trataba Elem ().
cc young