如何使用反射更改字段的结构片段?

I have the following 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}}
}

As you can see, some of the variables are an empty slice and some are not. For those that are empty, I need to add 1 struct of {"fred", 54}. For those slices that are not empty I need to change all values to {"fred", 54}. I do not know in advance what the fields are...only that if there is a string field the value should be "fred" and if an int field 54.

I'm able to change the value of "a" but everything else fails with "panic: reflect: call of reflect.Value.NumField on slice Value". I'm not sure where to go on this. Thank you!

As stated in the comments, you cannot use NumField on a slice, since that method is allowed only for reflect.Values that are of kind reflect.Struct.

So if you want to handle both kinds you need to know which one was passed in.

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

Now, if you want to append to an empty slice, you either have to pass in a pointer to the slice or you have to return the new slice.

change(&b)
change(&c)

Also, to be able to initialize that single element that you want to append you first need to know its type, to get the type of a slice's element you first get the slice's reflect.Type and then use its Elem method to get the type of the slice's element. With that type you can then use reflect.New to allocate a new value of that type and append it to the slice.

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

To then loop over a slice you can use the reflect.Value.Len and reflect.Value.Index methods.

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

The code:

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

The playground.