Generically modify struct fields using reflection in golang - go

Below is a method which uses reflect package to modify fields of a struct ,this works for a specific struct type
func modify(obj Car) interface{} {
ty := reflect.TypeOf(obj)
for i := 0; i < ty.NumField(); i++ {
rval := reflect.Indirect(reflect.ValueOf(&obj))
field := rval.Field(i)
fieldType := field.Kind()
switch fieldType {
case reflect.String:
field.SetString("")
case reflect.Int:
field.SetInt(0)
case reflect.Ptr:
field.Set(reflect.ValueOf(nil))
}
}
return obj
}
modifying the signature to
func modify(obj interface{}) interface{} {
results in
panic: reflect: call of reflect.Value.Field on interface Value
at line
field := rval.Field(i)
https://go.dev/play/p/pGfKtIg5RUp
It works with the signature
func modify(obj Car) interface{} {
https://go.dev/play/p/31Oh6WLmlGP
Why is the compile time type modifying the behaviour ?
The goal here is to mask certain fields based on struct tags .It could wrap an endpoint and the input and output to the method being wrapped could be struct or pointer so in above case both calls should work
modify(car)
modify(&car)

This is how it works for both value and pointer types
func modify(obj interface{}) interface{} {
rv := reflect.ValueOf(obj)
trv := reflect.TypeOf(obj)
value := reflect.New(rv.Type())
if rv.Kind() == reflect.Pointer {
rv = reflect.ValueOf(obj).Elem()
trv = reflect.TypeOf(obj).Elem()
value = reflect.New(rv.Type())
}
for i := 0; i < rv.NumField(); i++ {
field := rv.Field(i)
fieldType := field.Kind()
v := value.Elem().Field(i)
tag, _ := trv.Field(i).Tag.Lookup("es")
if len(tag) != 0 {
switch fieldType {
case reflect.String:
v.SetString(tag)
case reflect.Int:
v.SetInt(0)
case reflect.Ptr:
v.Set(reflect.ValueOf(nil))
}
} else {
v.Set(field)
}
}
return value
}
https://go.dev/play/p/C1pqw_UbPcG

Related

Nested string fields can not be updated using reflection in an arbitrary Go struct

I'm trying to update all string fields in a struct and its subfields using reflection in golang for an arbitrary struct as follows:
package main
import (
"fmt"
"reflect"
"strings"
)
func main() {
type Inner struct {
In1 string
In2 []string
}
type Type struct {
Name string
Names []string
NewSt Inner
}
a := Type{
Name: " [ (Amir[ ",
Names: nil,
NewSt: Inner{
In1: " [in1",
In2: []string{" [in2( "},
},
}
trims(&a)
fmt.Printf("%#v\n", a)
}
func trim(str string) string {
return strings.TrimSpace(strings.Trim(str, "[](){}, "))
}
func trims(ps interface{}) {
v := reflect.ValueOf(ps).Elem() // Elem() dereferences pointer
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
switch fv.Kind() {
case reflect.String:
fv.SetString(trim(fv.String()))
case reflect.Struct:
in := fv.Interface()
trims(&in)
}
}
}
But I get panic: reflect: call of reflect.Value.Elem on struct Value error.
How can I fix it or is there any better way that I can do such thing??
Thanks.
func trims(ps interface{}) {
v := reflect.ValueOf(ps)
if v.Kind() == reflect.Ptr {
v = v.Elem() // Elem() dereferences pointer
}
if v.Kind() != reflect.Struct {
panic("not struct")
}
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
switch fv.Kind() {
case reflect.String:
fv.SetString(trim(fv.String()))
case reflect.Struct:
// use Addr() to get an addressable
// value of the field
in := fv.Addr().Interface()
// do not use &in, that evaluates
// to *interface{}, that's almost
// NEVER what you want
trims(in)
case reflect.Slice:
if fv.Type().Elem().Kind() == reflect.String {
for i := 0; i < fv.Len(); i++ {
fv.Index(i).SetString(trim(fv.Index(i).String()))
}
}
}
}
}
https://go.dev/play/p/JkJTJzTckNA

Obtain structure info

The program is:
package main
import (
"fmt"
"reflect"
)
type Request struct {
Method string
Resource string //path
Protocol string
}
type s struct {
ID int
Title string
Request Request
Price float64
Interface interface{}
Exists bool
Many []string
}
func main() {
s := s{}
iterateStruct(s)
}
func iterateStruct(s interface{}) {
e := reflect.ValueOf(s)
for i := 0; i < e.NumField(); i++ {
varName := e.Type().Field(i).Name
varKind := e.Field(i).Kind()
fmt.Println(e.Type().Field(i).Name)
if varKind == reflect.Struct {
//iterateStruct( <what should be here?>)
}
varType := e.Type().Field(i).Type
varValue := e.Field(i).Interface()
fmt.Printf("%v %v %v %v\n", varName, varKind, varType, varValue)
}
}
Using recursion I'd like to get the same information for Request, that is a structure part of a structure.
What would I need to pass as a parameter? I tried various ways but I have to reckon it's a lot of trial and error for me.
Try this:
if varKind == reflect.Struct {
iterateStruct(e.Field(i).Interface())
}
e.Field(i) returns the Value for the struct field. Interface{} will return the underlying value, so you can call iterateStruct using that.
Here's an example that handles fields with pointers to structs, interfaces containing struct value, etc.. As a bonus, this example indents nested structs.
func iterate(v reflect.Value, indent string) {
v = reflect.Indirect(v)
if v.Kind() != reflect.Struct {
return
}
indent += " "
for i := 0; i < v.NumField(); i++ {
varName := v.Type().Field(i).Name
varKind := v.Field(i).Kind()
varType := v.Type().Field(i).Type
varValue := v.Field(i).Interface()
fmt.Printf("%s%v %v %v %v\n", indent, varName, varKind, varType, varValue)
iterate(v.Field(i), indent)
}
}
Call it like this:
iterate(reflect.ValueOf(s), "")
https://go.dev/play/p/y1CzbKAUvD_w

How do I change fields a slice of structs using reflect?

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.

Is it possible to use reflected array type in Golang type assertions?

I need to check if interface{} is an array and create corresponding slice if it is. Unfortunately I do not know array length in advance.
For example:
import (
"reflect"
)
func AnythingToSlice(a interface{}) []interface{} {
rt := reflect.TypeOf(a)
switch rt.Kind() {
case reflect.Slice:
slice, ok := a.([]interface{})
if ok {
return slice
}
// it works
case reflect.Array:
// return a[:]
// it doesn't work: cannot slice a (type interface {})
//
array, ok := a.([reflect.ValueOf(a).Len()]interface{})
// :-((( non-constant array bound reflect.ValueOf(a).Len()
if ok {
return array[:]
}
}
return []interface{}(a)
}
An explicit type is required in a type assertion. The type cannot be constructed through reflection.
Unless the argument is a []interface{}, the slice or array must be copied to produce a []interface{}.
Try this:
func AnythingToSlice(a interface{}) []interface{} {
v := reflect.ValueOf(a)
switch v.Kind() {
case reflect.Slice, reflect.Array:
result := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
result[i] = v.Index(i).Interface()
}
return result
default:
panic("not supported")
}
}
https://play.golang.org/p/3bXxnHOK8_

Access pointer value of a struct inside a function

I want to pass a struct object to a function & be able to access its pointer value from that function. I am not able to understand why the following is resulting in error.
func GetStructFieldPointers(u interface{}, jsonFields []string) []interface{} {
structVal := reflect.ValueOf(&u).Elem()
structType := reflect.TypeOf(u)
numberOfFields := structVal.NumField() // getting error here reflect:
// call of reflect.Value.NumField
// on interface Value
numberOfJSONFields := len(jsonFields)
res := make([]interface{}, numberOfJSONFields)
fmt.Println(jsonFields)
for fieldIndex, field := range jsonFields {
for i := 0; i < numberOfFields; i++ {
if structType.Field(i).Tag.Get("json") == field {
valueField := structVal.Field(i)
res[fieldIndex] = valueField.Addr().Interface()
}
}
}
return res
}
type User struct {
Id int `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
}
user := User{}
res := GetStructFieldPointers(user, []string{"id", "name"})
To make this work, I had to make structType as a parameter like following:
func GetStructFieldPointers(u interface{}, structType reflect.Type, jsonFields []string) []interface{} {
structVal := reflect.ValueOf(u).Elem()
// structType := reflect.TypeOf(u)
numberOfFields := structVal.NumField()
numberOfJSONFields := len(jsonFields)
res := make([]interface{}, numberOfJSONFields)
fmt.Println(jsonFields)
for fieldIndex, field := range jsonFields {
for i := 0; i < numberOfFields; i++ {
if structType.Field(i).Tag.Get("json") == field {
valueField := structVal.Field(i)
res[fieldIndex] = valueField.Addr().Interface()
}
}
}
return res
}
user := User{}
res := GetStructFieldPointers(&user, reflect.TypeOf(user), []string{"id", "name"})
I like to know how to pass User{} as a parameter & use in both reflect.ValueOf & reflect.TypeOf calls.
On this line: structVal := reflect.ValueOf(&u).Elem() you're taking the address of an interface (the argument of your func) and not an address to the interface's underlying value, and then you're passing the pointer to ValueOf, so the .Elem() call returns the "elem value" to which the pointer points to, which is the interface, not struct.
If you know for a fact that the passed in value is a struct and not a pointer, all you need is this: structVal := reflect.ValueOf(u).
If a pointer was passed to your func, eg GetStructFieldPointers(&u, ... then this is what you want: structVal := reflect.ValueOf(u).Elem().
But also you can handle both cases by checking the value's kind.
rv := reflect.ValueOf(u)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() == reflect.Struct {
fmt.Println(rv.NumField())
}
https://play.golang.org/p/9F9LNnwEaH
Update:
Took a better look at your code... If you want to be able to get the addresses of your struct's fields, you need to pass a pointer to the struct as the argument, or else those fields are going to be unaddressable.
https://play.golang.org/p/RaA2rau3s-

Resources