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.Value
s 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.