Structs may contains float32, int32, string or pointer to struct.
Here is my code. But i do not know how to assign values to the structs inside.
type T struct {
A int
B string
C *P
}
type P struct {
A string
B int
}
func main() {
t := T{}
decode(&t, []string{"99", "abc", "abc", "99"})
fmt.Println(t)
}
You could do something like this:
func decode(a interface{}, val []string) {
rdecode(reflect.ValueOf(a).Elem(), val)
}
func rdecode(rv reflect.Value, val []string) int {
var index int
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if f.Kind() == reflect.Ptr {
if f.IsNil() {
f.Set(reflect.New(f.Type().Elem()))
}
f = f.Elem()
}
switch f.Kind() {
case reflect.Int:
tmp, _ := strconv.Atoi(val[index])
f.Set(reflect.ValueOf(tmp))
index++
case reflect.String:
f.SetString(val[index])
index++
case reflect.Struct:
index += rdecode(f, val[index:])
default:
break
}
}
return index
}
https://play.golang.com/p/Jyj0flzDBhu
Related
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
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 two different struct as mentioned below A abd B and two process functions. Is there any way by means of which i can write a common function to generate the map[string]struct for the both the struct. Moreover, is there any way using reflection given the struct name i can create the object of the same?
type A struct {
name string
// more fields
}
type B struct {
name string
// more fields
}
func ProcessA(input []A) map[string]A {
output := make(map[string]A)
for _, v := range input {
output[v.name] = v
}
return output
}
func ProcessB(input []B) map[string]B {
output := make(map[string]B)
for _, v := range input {
output[v.name] = v
}
return output
}
Idiomatic way in Go would be to use interface.
type Named interface {
Name() string
}
type letter struct {
name string
}
func (l letter) Name() string {
return l.name
}
type A struct {
letter
// more fields
}
type B struct {
letter
// more fields
}
func ProcessNameds(input []Named) map[string]Named {
output := make(map[string]Named, len(input))
for _, v := range input {
output[v.Name()] = v
}
return output
}
Well, see if something like this would help:
package main
import (
"fmt"
"strconv"
)
type A struct {
name string
// more fields
}
type B struct {
name string
// more fields
}
func Process(x interface{}) interface{} {
ma := make(map[string]int)
mb := make(map[string]string)
if x == nil {
return nil
} else if a, ok := x.([]A); ok {
fmt.Printf("Type A argument passed %s\n", x)
ma[a[0].name] = 1
ma[a[1].name] = 2
return ma //you can return whatever type you want here
} else if b, ok := x.([]B); ok {
fmt.Printf("Type B argument passed %s\n", x)
mb[b[0].name] = "a"
mb[b[1].name] = "b"
return mb //you can return whatever type you want here
} else {
panic(fmt.Sprintf("Unexpected type %T: %v", x, x))
}
return nil
}
func main() {
a := make([]A, 5)
for i := 0; i < len(a); i++ {
a[i].name = strconv.Itoa(i) + "A"
}
b := make([]B, 7)
for i := 0; i < len(b); i++ {
b[i].name = strconv.Itoa(i) + "B"
}
fmt.Println(Process(a))
fmt.Println(Process(b))
//Uncomment line below to see the panic
//fmt.Println(Process(8))
}
https://play.golang.org/p/irdCsbpvUv_t
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 have two structs:
type A struct {
BankCode string `json:"bankCode"`
BankName string `json:"bankName"`
}
And:
type B struct {
A
extra string `json:" extra"`
}
And two slices:
listsA []A and listsB []B
I want to get bankCodes from listA and listB. bankcodes only contains bankcodes. It is a []string
It will be so easy as using two function.
func getBankCodes(data []A) []string {
res := make([]string, len(data))
for i := 0; i < len(data); i++ {
res[i] = data[i].BankCode
}
return res
}
func getBankCodes(data []B) []string {
res := make([]string, len(data))
for i := 0; i < len(data); i++ {
res[i] = data[i].BankCode
}
return res
}
How to use one common function ?
Well the clean solution would be to use an interface, since go doesn't support classic inheritance, so something like []parentclass can't work. Interfaces however can only describe functions not a common field, so you have to implement a Getter (essentially).
// GetBankCoder provides a function that gives the BankCode
type GetBankCoder interface {
getBankCode() string
}
// implement GetBankCoder for A (and indirectly for B)
func (a A) getBankCode() string {
return a.BankCode
}
and make your getBankCodes work on that interface type, notice the parameter of the function as well as the statement inside the loop:
func getBankCodes(data []GetBankCoder) []string { // <-- changed
res := make([]string, len(data))
for i := 0; i < len(data); i++ {
res[i] = data[i].getBankCode() // <-- changed
}
return res
}
There are other solutions where the function parameter is of interface{} type and then reflection is used to assure you can actually do .BankCode, but I don't like those, as they are not adding more clarity either.
... However, I couldn't get the golang playground to make this work correctly without putting it into a []GetBankCoder var first, before giving it to the function.
banks := make([]GetBankCoder, 0)
banks = append(banks, A{ BankCode: "ABC", BankName: "ABC Bank"})
getBankCodes(banks)
You may use one common function like so:
func BankCodes(data interface{}) []string {
if reflect.TypeOf(data).Kind() != reflect.Slice {
panic("err: data is not slice")
}
slice := reflect.Indirect(reflect.ValueOf(data))
res := make([]string, slice.Len())
for i := 0; i < slice.Len(); i++ {
a := slice.Index(i).Interface().(BankCoder)
res[i] = a.Bankcode()
}
return res
}
Code (try on The Go Playground):
package main
import (
"fmt"
"reflect"
)
func main() {
bs := []B{B{A{"BC1", "BN"}, "e"}, B{A{"BC2", "BN"}, "e"}}
strs := BankCodes(bs)
fmt.Println(strs)
as := []A{A{"AC1", "BN"}, A{"AC2", "BN"}}
strs2 := BankCodes(as)
fmt.Println(strs2)
}
func BankCodes(data interface{}) []string {
if reflect.TypeOf(data).Kind() != reflect.Slice {
panic("err: data is not slice")
}
slice := reflect.Indirect(reflect.ValueOf(data))
res := make([]string, slice.Len())
for i := 0; i < slice.Len(); i++ {
a := slice.Index(i).Interface().(BankCoder)
res[i] = a.Bankcode()
}
return res
}
type A struct {
BankCode string `json:"bankCode"`
BankName string `json:"bankName"`
}
type B struct {
A
extra string `json:" extra"`
}
type BankCoder interface {
Bankcode() string
}
func (a A) Bankcode() string {
return a.BankCode
}