Usando refletir, como você define o valor de um campo de estrutura?

106

tendo dificuldade em trabalhar com campos de estrutura usando reflectpacote. em particular, não descobri como definir o valor do campo.

tipo t struct {fi int; string fs}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. obter o nome do campo i - parece funcionar

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

  2. obter o valor do campo i como a) interface {}, b) int - parece funcionar

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

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

  3. definindo valor do campo i - tente um - pânico

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

    panic : reflect.Value · SetInt usando o valor obtido usando o campo não exportado

    presumindo que não gostasse dos nomes de campo "id" e "nome", então renomeado para "Id" e "Nome"

    a) esta suposição está correta?

    b) se correto, achei desnecessário já que no mesmo arquivo / pacote

  4. definir o valor do campo i - tente dois (com os nomes dos campos em maiúsculas) - pânico

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

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

    panic : reflect.Value · SetInt usando valor não endereçável


As instruções abaixo por @peterSO são completas e de alta qualidade

Quatro. isso funciona:

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

Ele documenta também que os nomes dos campos devem ser exportáveis ​​(começar com letra maiúscula)

cc jovem
fonte
o exemplo mais próximo que consegui encontrar de alguém usando reflectpara definir dados foi comments.gmane.org/gmane.comp.lang.go.general/35045 , mas mesmo lá ele costumava json.Unmarshalfazer o trabalho sujo
cc jovem
(o comentário acima é obsoleto)
cc young

Respostas:

156

Go está disponível como código-fonte aberto . Uma boa maneira de aprender sobre reflexão é ver como os principais desenvolvedores de Go a usam. Por exemplo, os pacotes Go fmt e json . A documentação do pacote possui links para os arquivos de código-fonte sob o título Arquivos de pacote.

O pacote Go json empacota e descompacta estruturas JSON de e para Go.


Aqui está um exemplo passo a passo que define o valor de um structcampo enquanto evita erros cuidadosamente.

O reflectpacote Go tem uma CanAddrfunção.

func (v Value) CanAddr() bool

CanAddr retorna true se o endereço do valor puder ser obtido com Addr. Esses valores são chamados de endereçáveis. Um valor é endereçável se for um elemento de uma fatia, um elemento de uma matriz endereçável, um campo de uma estrutura endereçável ou o resultado da desreferenciação de um ponteiro. Se CanAddr retornar falso, chamar Addr entrará em pânico.

O reflectpacote Go tem uma CanSetfunção que, se true, implica que CanAddrtambém é true.

func (v Value) CanSet() bool

CanSet retorna verdadeiro se o valor de v puder ser alterado. Um valor pode ser alterado apenas se for endereçável e não foi obtido pelo uso de campos de estrutura não exportados. Se CanSet retornar falso, chamar Set ou qualquer setter específico do tipo (por exemplo, SetBool, SetInt64) entrará em pânico.

Precisamos ter certeza de que nós podemos Seto structcampo. Por exemplo,

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

Se pudermos ter certeza de que todas as verificações de erro são desnecessárias, o exemplo simplifica para,

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
fonte
5
Desistir! em algum lugar há uma resposta, mas quatro horas de trabalho no pacote json não renderam para mim. Sobre o pacote de reflexão, extrair informações é bastante simples, mas definir dados requer um pouco de magia negra, da qual eu adoraria ver um exemplo simples em algum lugar!
cc jovem de
2
Excepcional! Se você estiver na Tailândia, deixe-me oferecer uma cerveja, duas ou três! muito obrigado
cc jovem de
5
Grande exemplo prático, este artigo desmistificou completamente para mim golang.org/doc/articles/laws_of_reflection.html
danmux
Exemplo incrível
Sarath Sadasivan Pillai
Isso não define um campo de estrutura. Isso define um campo em um ponteiro para struct. Obviamente, isso não está respondendo à pergunta feita por OP.
wvxvw
14

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

Impressões:

123
321
Asgeir
fonte
obrigado! agora que li as notas de PeterSO, isso faz todo o sentido. Eu estava usando foo, não & foo, então não podia ser alterado e não tinha certeza do que se tratava Elem ().
cc jovem de