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
Let's say I have an instance of struct Thing1 that I want to json.Marshal
type Thing1 struct {
A string `json:"a,omitempty"`
B int `json:"b,omitempty"`
C Thing2 `json:"c,omitempty"`
}
type Thing2 struct {
D bool `json:"d,omitempty"`
E int `json:"e,omitempty"`
}
...
thing1 := Thing1{
A: "test",
B: 42,
C: Thing2{D: true, E: 43},
}
How would you write a function that takes an instance of any struct and a list of fields to redact and returns a clone (or just mutates) of the incoming object, but with the redacted fields set to their zero values?
redact(thing1, []string{"B", "D"})
thing1 == Thing1{
A: "test",
B: 0,
C: Thing2{D: false, E: 43},
}
I can't use json:"-" as a field tag because the current ones in place are required for the query language I am using (Dgraph).
edit: not in the example, but objects inside arrays should also be redacted if applicable
Use reflect to manipulate the value of struct's field. Below is a proof of concept from what I have written in the comment. Since this is just a poc, you might need to adjust/modify the code to follow your needs.
This function mutate the original data. Code is self explanatory.
func redact(target interface{}, fieldsToModify []string) {
// if target is not pointer, then immediately return
// modifying struct's field requires addresable object
addrValue := reflect.ValueOf(target)
if addrValue.Kind() != reflect.Ptr {
return
}
// if target is not struct then immediatelly return
// this might need to be modified as per your needs
targetValue := addrValue.Elem()
targetType := targetValue.Type()
if targetType.Kind() != reflect.Struct {
return
}
// loop the fields
for i := 0; i < targetType.NumField(); i++ {
fType := targetType.Field(i)
fValue := targetValue.Field(i)
// if the field type is struct, then call redact() recursively
if fValue.Kind() == reflect.Struct {
redact(fValue.Addr().Interface(), fieldsToModify)
continue
}
// if the field is slice, loop then call redact() recursively
if fValue.Kind() == reflect.Array || fValue.Kind() == reflect.Slice {
for i := 0; i < fValue.Len(); i++ {
redact(fValue.Index(i).Addr().Interface(), fieldsToModify)
}
continue
}
// loop the fieldsToModify
for _, fieldToModify := range fieldsToModify {
if fieldToModify == fType.Name && fValue.CanSet() {
fValue.Set(reflect.Zero(fType.Type))
}
}
}
}
The redact() function pointer data in first parameter, since modifying fields require addresable object.
type Thing2 struct {
D bool `json:"d,omitempty"`
E int `json:"e,omitempty"`
}
type Thing1 struct {
A string `json:"a,omitempty"`
B int `json:"b,omitempty"`
C Thing2 `json:"c,omitempty"`
H []Thing2 `json:"h,omitempty"`
}
thing1 := Thing1{
A: "test",
B: 42,
C: Thing2{D: true, E: 43},
H: []Thing2{Thing2{D: true, E: 43}},
}
fmt.Printf("before: %#v \n", thing1)
// before: main.Thing1{A:"test", B:42, C:main.Thing2{D:true, E:43}, H:[]main.Thing2{main.Thing2{D:true, E:43}}}
redact(&thing1, []string{"B", "D"})
fmt.Printf("after: %#v \n", thing1)
// after: main.Thing1{A:"test", B:0, C:main.Thing2{D:false, E:43}, H:[]main.Thing2{main.Thing2{D:false, E:43}}}
Playground: https://play.golang.org/p/wy39DGdSVV7
Here's how to do it with the reflect package:
func redact(x interface{}, names []string) error {
// Starting value must be a pointer.
v := reflect.ValueOf(x)
if v.Kind() != reflect.Ptr {
return errors.New("not pointer")
}
// Create map for easy lookup.
m := make(map[string]bool)
for _, name := range names {
m[name] = true
}
redactValue(v, m)
return nil
}
func redactValue(v reflect.Value, names map[string]bool) {
switch v.Kind() {
case reflect.Ptr:
if v.IsZero() {
return
}
redactValue(v.Elem(), names)
case reflect.Interface:
if v.IsZero() {
return
}
iv := v.Elem()
switch iv.Kind() {
case reflect.Slice, reflect.Ptr:
redactValue(iv, names)
case reflect.Struct, reflect.Array:
// Copy required for modification.
copy := reflect.New(iv.Type()).Elem()
copy.Set(iv)
redactValue(copy, names)
v.Set(copy)
}
case reflect.Struct:
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
ft := sf.Type
fv := v.Field(i)
if names[sf.Name] {
// Clobber the field.
fv.Set(reflect.Zero(ft))
continue
}
redactValue(fv, names)
}
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
redactValue(v.Index(i), names)
}
}
}
Run it on the playground.
This answer handles structs, slices, arrays, pointers and interfaces.
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.
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_
If I have a struct like this:
var Foo struct {
Bar struct {
blah *bool
}
}
And I send the struct to a function that takes an interface as a parameter, is there an easy way to use reflection to find the field "blah" by name using inVal.FieldByName("blah")?
Here's one way to do it:
func findField(v interface{}, name string) reflect.Value {
// create queue of values to search. Start with the function arg.
queue := []reflect.Value{reflect.ValueOf(v)}
for len(queue) > 0 {
v := queue[0]
queue = queue[1:]
// dereference pointers
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
// ignore if this is not a struct
if v.Kind() != reflect.Struct {
continue
}
// iterate through fields looking for match on name
t := v.Type()
for i := 0; i < v.NumField(); i++ {
if t.Field(i).Name == name {
// found it!
return v.Field(i)
}
// push field to queue
queue = append(queue, v.Field(i))
}
}
return reflect.Value{}
}
playground example