Dengan menggunakan reflect, bagaimana Anda menyetel nilai bidang struct?

106

mengalami kesulitan bekerja dengan bidang struct menggunakan reflectpaket. khususnya, belum menemukan cara mengatur nilai bidang.

ketik t struct {fi int; fs string}
var rt = t {123, "jblow"}
var i64 int64 = 456
  1. mendapatkan Nama bidang i - ini sepertinya berhasil

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

  2. mendapatkan nilai bidang i sebagai a) antarmuka {}, b) int - ini sepertinya berhasil

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

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

  3. nilai pengaturan bidang i - coba satu - panik

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

    panic : reflect.Value · SetInt menggunakan nilai yang diperoleh menggunakan bidang yang tidak diekspor

    dengan asumsi tidak suka nama kolom "id" dan "name", maka diganti namanya menjadi "Id" dan "Name"

    a) apakah asumsi ini benar?

    b) jika benar, dianggap tidak perlu karena dalam file / paket yang sama

  4. nilai pengaturan bidang i - coba dua (dengan nama bidang dikapitalisasi) - panik

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

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

    panic : reflect.Value · SetInt using unaddressable value


Petunjuk di bawah oleh @peterSO dilakukan secara menyeluruh dan berkualitas tinggi

Empat. ini bekerja:

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

Ia mendokumentasikan juga bahwa nama bidang harus dapat diekspor (dimulai dengan huruf kapital)

cc muda
sumber
contoh terdekat yang dapat saya temukan untuk seseorang yang menggunakan reflectuntuk mengatur data adalah comments.gmane.org/gmane.comp.lang.go.general/35045 , tetapi bahkan di sana dia biasa json.Unmarshalmelakukan pekerjaan kotor yang sebenarnya
cc muda
(komentar di atas sudah usang)
cc muda

Jawaban:

156

Go tersedia sebagai kode sumber terbuka . Cara yang baik untuk mempelajari refleksi adalah dengan melihat bagaimana pengembang inti Go menggunakannya. Misalnya, paket Go fmt dan json . Dokumentasi paket memiliki tautan ke file kode sumber di bawah judul File paket.

Go json package marshal dan unmarshals JSON dari dan ke struktur Go.


Berikut adalah contoh langkah demi langkah yang menetapkan nilai structbidang sambil menghindari kesalahan dengan hati-hati.

reflectPaket Go memiliki CanAddrfungsi.

func (v Value) CanAddr() bool

CanAddr mengembalikan nilai true jika alamat nilai dapat diperoleh dengan Addr. Nilai-nilai seperti itu disebut dapat dialamatkan. Sebuah nilai dapat dialamatkan jika itu adalah elemen dari sebuah slice, sebuah elemen dari sebuah array yang dapat dialamatkan, sebuah field dari sebuah struct yang dapat dialamatkan, atau hasil dari dereferensi sebuah pointer. Jika CanAddr mengembalikan false, memanggil Addr akan membuat panik.

reflectPaket Go memiliki CanSetfungsi, yang, jika true, menyiratkannya CanAddrjuga true.

func (v Value) CanSet() bool

CanSet mengembalikan nilai true jika nilai v dapat diubah. Nilai dapat diubah hanya jika dapat dialamatkan dan tidak diperoleh dengan menggunakan bidang struct yang tidak diekspor. Jika CanSet mengembalikan nilai false, memanggil Set atau penyetel khusus tipe apa pun (misalnya, SetBool, SetInt64) akan panik.

Kita perlu memastikan kita bisa Setdi structlapangan. Sebagai contoh,

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

Jika kita dapat yakin bahwa semua pemeriksaan kesalahan tidak diperlukan, contoh disederhanakan menjadi,

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
sumber
5
Menyerah! di suatu tempat ada jawaban, tetapi empat jam kerja di json pkg belum memberikannya kepada saya. Untuk mencerminkan pkg, menarik info cukup mudah, tetapi pengaturan data memerlukan beberapa ilmu hitam yang ingin saya lihat contoh sederhananya di suatu tempat!
cc muda
2
Luar biasa! jika Anda pernah ke Thailand, izinkan saya mentraktir Anda satu atau dua atau tiga bir! terima kasih banyak
cc muda
5
Contoh praktis yang bagus, artikel ini benar-benar mengungkapnya untuk saya golang.org/doc/articles/laws_of_reflection.html
danmux
Contoh yang luar biasa Berikut
Sarath Sadasivan Pillai
Ini tidak menyetel bidang struct. Ini menyetel bidang di pointer ke struct. Jelas, ini tidak menjawab pertanyaan OP yang diajukan.
wvxvw
14

Ini sepertinya berhasil:

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

Cetakan:

123
321
Asgeir
sumber
Terima kasih! sekarang setelah saya membaca catatan peterSO, ini sangat masuk akal. Saya menggunakan foo, bukan & foo, jadi tidak dapat diubah, dan tidak yakin tentang apa Elem ().
cc muda