【问题标题】:How do I change fields a slice of structs using reflect?如何使用反射更改字段切片结构?
【发布时间】:2018-04-21 14:39:36
【问题描述】:

我有以下https://play.golang.org/p/TlHCX29QZr

package main

import (
    "fmt"
    "reflect"
)

type A struct {
    Name string
    Age  int
}

func change(a interface{}) {
    aa := reflect.Indirect(reflect.ValueOf(a))

    for i := 0; i < aa.NumField(); i++ {
        field := aa.Field(i)

        switch field.Interface().(type) {
        case string:
            field.Set(reflect.ValueOf("fred"))
        case int:
            field.Set(reflect.ValueOf(54))
        default:
            fmt.Println("unknown field")
        }
    }
}

func main() {
    a := &A{"bob", 120}
    b := []*A{}
    c := []struct {
        Alias  string
        Months int
    }{}

    d := []struct {
        First  string
        Years int
    }{
        {"james", 22},
        {"ricky", 32},
        {"bobby", 12},
        {"rachel", 82}, 
    }


    change(a)
    fmt.Println(a) // want &A{"fred", 54}

    change(b)
    fmt.Println(b) // want []*A{&A{"fred", 54}}

    change(c)
    fmt.Println(c) // want []struct{struct{"fred", 54}}

    change(d)
    fmt.Println(d) // want []struct{struct{"fred", 54}, struct{"fred", 54}, struct{"fred", 54}, struct{"fred", 54}}
}

如您所见,有些变量是空切片,有些则不是。对于那些为空的,我需要添加 1 个结构 {"fred", 54}。对于那些不为空的切片,我需要将所有值更改为 {"fred", 54}。我事先不知道这些字段是什么......只是如果有一个字符串字段,则值应该是“fred”,如果一个 int 字段是 54。

我能够更改“a”的值,但其他一切都失败了,因为“恐慌:反映:在切片值上调用 reflect.Value.NumField”。我不知道该去哪里。谢谢!

【问题讨论】:

  • 你传入了两个不同的reflect.Kinds,结构和切片,如果切片你不能使用NumField,你必须遍历它的内容,确保那些是结构和那么你可以在这些上拨打NumField
  • 逻辑上你还想在这里做两件不同的事情。在 b 和 c 中,如果切片为空,您希望“附加”一个值,而在另一个中,您希望修改值。
  • @mkopriva 如何遍历一片空的匿名结构来获取字段名称?

标签: go


【解决方案1】:

如 cmets 中所述,您不能在切片上使用 NumField,因为该方法仅适用于类型为 reflect.Structreflect.Values。

因此,如果您想同时处理这两种类型,您需要知道传入的是哪一种。

if rv.Kind() == reflect.Struct {
    changeStruct(rv)
}
if rv.Kind() == reflect.Slice {
    changeSlice(rv)
}

现在,如果你想追加到一个空切片,你要么必须传入指向切片的指针,要么必须返回新切片。

change(&b)
change(&c)

另外,为了能够初始化你想要附加的单个元素,你首先需要知道它的类型,要获得切片元素的类型,你首先要获得切片的reflect.Type,然后使用它的Elem 方法获取切片元素的类型。使用该类型,您可以使用reflect.New 分配该类型的新值并将其附加到切片。

var elem reflect.Value

// rv is the slice
typ := rv.Type().Elem()
if typ.Kind() == reflect.Ptr {
    elem = reflect.New(typ.Elem())
}
if typ.Kind() == reflect.Struct {
    elem = reflect.New(typ).Elem()
}

然后循环切片,您可以使用reflect.Value.Lenreflect.Value.Index 方法。

ln := rv.Len()
for i := 0; i < ln; i++ {
    changerv(rv.Index(i))
}

代码:

func change(a interface{}) {
    rv := reflect.ValueOf(a)
    changerv(rv)
}

func changerv(rv reflect.Value) {
    if rv.Kind() == reflect.Ptr {
        rv = rv.Elem()
    }

    if rv.Kind() == reflect.Struct {
        changeStruct(rv)
    }
    if rv.Kind() == reflect.Slice {
        changeSlice(rv)
    }
}

// assumes rv is a slice
func changeSlice(rv reflect.Value) {
    ln := rv.Len()
    if ln == 0 && rv.CanAddr() {
        var elem reflect.Value

        typ := rv.Type().Elem()
        if typ.Kind() == reflect.Ptr {
            elem = reflect.New(typ.Elem())
        }
        if typ.Kind() == reflect.Struct {
            elem = reflect.New(typ).Elem()
        }

        rv.Set(reflect.Append(rv, elem))
    }

    ln = rv.Len()
    for i := 0; i < ln; i++ {
        changerv(rv.Index(i))
    }
}

// assumes rv is a struct
func changeStruct(rv reflect.Value) {
    if !rv.CanAddr() {
        return
    }
    for i := 0; i < rv.NumField(); i++ {
        field := rv.Field(i)

        switch field.Kind() {
        case reflect.String:
            field.SetString("fred")
        case reflect.Int:
            field.SetInt(54)
        default:
            fmt.Println("unknown field")
        }
    }
}

playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-06
    • 2020-07-21
    • 1970-01-01
    • 2020-12-31
    • 2016-10-11
    • 2021-02-19
    • 2020-02-03
    • 1970-01-01
    相关资源
    最近更新 更多