How to put various structs in a list and manipulate? - go

I want to manipulate various structs' field which has same name and type programmatically like the following but I have no idea how to put varied structs in a list.
package main
import "fmt"
type A struct {
Cnt int
}
type B struct {
Cnt int
}
func main() {
a := &A{}
b := &B{}
list := []something{
a,
b,
}
for _, item := range list {
item.Cnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
}

Declare a common interface for the types. The methods should reflect whatever action you want to executed on the values. I use add here as generalization of increment.
type Cntr interface {
Add(i int)
}
Implement that interface on each type:
func (a *A) Add(i int) { a.Cnt += i }
func (b *B) Add(i int) { b.Cnt += i }
Declare slice of interface type and with values of types *A and *B:
a := &A{}
b := &B{}
list := []Cntr{ // <-- slice of the interface type
a,
b,
}
Increment the counters:
for _, item := range list {
item.Add(1)
}
Print the results:
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
// prints a.Cnt: 1, b.Cnt: 1
Run this playground on the program.

Use the reflect API to get a pointer to a named field in an arbitrary struct type:
func getFieldPtr(v interface{}, name string) interface{} {
return reflect.ValueOf(v).Elem().FieldByName(name).Addr().Interface()
}
Use it like this:
a := &A{}
b := &B{}
list := []interface{}{
a,
b,
}
for _, item := range list {
pcnt := getFieldPtr(item, "Cnt").(*int)
*pcnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
https://go.dev/play/p/InVlnv37yqW

Related

golang how to access promoted type

I have a 'common' structure promoted within two specific structures. For example:
type common struct {
name string
}
type apple struct {
common
}
type orange struct {
common
}
Details specific to apple and orange are omitted.
I have a type-specific map of each, e.g., map[string]*apple and map[string]*orange.
I am trying to make a single function that can extract the common pointers. From what I've tried so far, reflection appears required.
My function is:
func getFruitArray(theMap interface{}) []*common {
m := reflect.ValueOf(theMap)
cf := make([]*common, 0, m.Len())
for _, mk := range m.MapKeys() {
v := m.MapIndex(mk)
cf = append(cf, v.Interface().(*common))
}
return cf
}
This function fails at cf = append(cf, v.Interface().(*common)) with:
panic: interface conversion: interface {} is *main.apple, not *main.common
Is there a way to access the promoted struct common without specifically referencing apple or orange in this function?
playground example
See Burak's answer which makes the reasonable compromise of having to call a method to receive the value.
Regardless, below is a solution which uses reflection as you planned. Note that common needs to be Common (exported field) else the reflect package cannot read it.
package main
import (
"log"
"reflect"
)
type Common struct {
name string
}
type apple struct {
Common
}
type orange struct {
Common
}
func getFruitArray(theMap interface{}) []*Common {
m := reflect.ValueOf(theMap)
cf := make([]*Common, 0, m.Len())
for _, mk := range m.MapKeys() {
v := m.MapIndex(mk)
f := v.Elem().FieldByName("Common")
cf = append(cf, f.Addr().Interface().(*Common))
}
return cf
}
func main() {
appleMap := make(map[string]*apple)
orangeMap := make(map[string]*orange)
a1 := &apple{}
a1.name = "my apple"
appleMap["test"] = a1
o1 := &orange{}
o1.name = "my orange"
orangeMap["test2"] = o1
f1 := getFruitArray(appleMap)
for _, c := range f1 {
log.Printf("f1: %s", c.name)
}
f2 := getFruitArray(orangeMap)
for _, c := range f2 {
log.Printf("f2: %s", c.name)
}
}
https://go.dev/play/p/FrkRnu_G2Xd
You don't need reflection. One way is to use an interface:
type common struct {
name string
tag string
}
func (c *common) GetCommon() *common {return c}
type WithCommon interface {
GetCommon() *common
}
Then you can do:
func getFruitArray(theMap map[string]WithCommon) []*common {
cf := make([]*common, 0, theMap.Len())
for _,k:=range theMap {
cf=append(cf,k.GetCommon())
}
return cf
}
But you also have to do:
appleMap := make(map[string]WithCommon)
orangeMap := make(map[string]WithCommon)
If you know what you are doing you could use the unsafe package. But if you don't then don't.
func getFruitArray(theMap interface{}) []*common {
m := reflect.ValueOf(theMap)
cf := make([]*common, 0, m.Len())
for _, mk := range m.MapKeys() {
v := m.MapIndex(mk).Elem() // use elem to dereference the pointer
t := v.Type()
// If you know that common is always the first field
// then you can just use v.Field(0). But if common's
// position is not guaranteed then use a loop like below.
for i := 0; i < v.NumField(); i++ {
sf := t.Field(i)
if sf.Anonymous && sf.Name == "common" {
f := v.Field(i)
// 1. get the address of the common field
// 2. convert it first to unsafe.Pointer
// 3. then convert it to *common
c := (*common)(unsafe.Pointer(f.UnsafeAddr()))
cf = append(cf, c)
}
}
}
return cf
}
https://go.dev/play/p/XMi86jj2wiW

Instantiate a new obj using Go reflect and type assert to an interface

I can't explain why the following is working.
package main
import (
"fmt"
"reflect"
"strings"
)
type MyInterface interface {
someFunc()
}
type Dock struct {
}
func (d *Dock) someFunc() {
}
type Group struct {
Docks []Dock `better:"sometag"`
}
func foo(model interface{}) {
v1 := reflect.Indirect(reflect.ValueOf(model))
for i := 0; i < v1.NumField(); i++ {
tag := v1.Type().Field(i).Tag.Get("better")
if strings.HasPrefix(tag, "sometag") {
inter := v1.Field(i).Interface()
typ := reflect.TypeOf(inter).Elem()
fmt.Println("Type:", typ.String())
// Want to instantiate type like &Dock{} then assign it to some interface,
// but using reflect
n := reflect.New(typ)
_, ok := n.Interface().(MyInterface)
fmt.Println("Why is it OK?", ok)
}
}
}
func main() {
g := &Group{}
foo(g)
/*var v1, v2 interface{}
d1 := &Dock{}
v1 = d1
_, ok1 := v1.(MyInterface)
d2 := Dock{}
v2 = d2
_, ok2 := v2.(MyInterface)
fmt.Println(ok1, ok2)*/
}
It prints
Type: main.Dock
OK? true
If it's a Dock type, then it's not a pointer to Dock. Why does it conforms to MyInterface?
https://play.golang.org/p/Z9mR8amYOM7
Where as the d2 example in the comment does not.
In go doc for reflect.New
New returns a Value representing a pointer to a new zero value for the
specified type. That is, the returned Value's Type is PtrTo(typ).
n := reflect.New(typ)
fmt.Println("Type:", n.String())
It will print Type: <*main.Dock Value> means n is a pointer of Dock.You miss the part using reflect.New return the pointer.

Golang reflection MethodByName()

I write the code that fills data structures depending on its type. I need to call nested struct function if it exists.
Why I get zero value looking for function while the field is correct?
type (
SomeData struct {
Val NestedType
}
NestedType struct {
V1 string
}
)
func (t *NestedType) FillData(v int) {
t.V1 = fmt.Sprintf("Here is %v", v)
}
func main() {
i := SomeData{}
reflect.ValueOf(&i.Val).MethodByName("FillData").Call([]reflect.Value{reflect.ValueOf(555)})
fmt.Println(i) /// {{I hate 555}}
// BUT!
v := 666
newObj := reflect.New(reflect.TypeOf(SomeData{}))
fVal := newObj.Elem().FieldByName("Val")
fmt.Println( "fVal.NumField():", fVal.NumField()) //fVal.NumField(): 1
f := fVal.MethodByName("FillData")
f.Call([]reflect.Value{reflect.ValueOf(v)}) //panic: reflect: call of reflect.Value.Call on zero Value
}
The method is on the pointer receiver. The value fVal is a NestedType. Call Value.Addr to get a *NestedType:
f := fVal.Addr().MethodByName("FillData")
Run it on the playground.

How to cast interface{} to a map in GO

I am new to Go & I am trying to learn how to cast interface{} to a Map. Here is an example of what I am trying to implement.
Playground link : https://play.golang.org/p/3jhKlGKO46Z
Thanks for the help.
package main
import (
"fmt"
)
func main() {
Map := make(map[string]interface{})
test(Map)
for k,v := range Map {
fmt.Println("key : %v Value : %v", k, v)
}
}
func test(v interface{}) error{
data := make(map[int]interface{})
for i := 0; i < 10; i++ {
data[i] = i * 5
}
for key,val := range data {
// fmt.Println("key : %v Value : %v", key, val)
v[key] = val
}
return nil
Go supports type assertions for the interfaces. It provides the concrete value present in the interface.
You can achieve this with following code.
m, ok := v.(map[int]interface{})
if ok {
// use m
_ = m
}
If the asserted value is not of given type, ok will be false
If you avoid second return value, the program will panic for wrong assertions.
I strongly recommend you to go through https://tour.golang.org

How to through struct's fieldName get value

type mcat struct {
ID int
}
type cat struct {
Name string
M mcat
}
func getValue(path string, mcat cat){
//throuth struct path get the value
}
func main(){
mycat := cat{"cat", mcat{1}}
id := getvalue("/M/ID", mycat)
}
Can I do this by reflecting to get a value based on the field name?
You may do what you want with the Value.FieldByName() function. Just range over the parts of the path which may be splitted using strings.Split().
Here's an example:
func getValue(i interface{}, path string) interface{} {
v := reflect.ValueOf(i)
for _, field := range strings.Split(path[1:], "/") {
v = v.FieldByName(field)
}
return v.Interface()
}
func main() {
mycat := cat{"cat", mcat{1}}
id := getValue(mycat, "/M/ID")
fmt.Println(id)
}
It outputs (try it on the Go Playground):
1
Some things to note:
The above solution works for all struct types, not just with cat. Checks if the passed value is a struct or the field exists is omitted.
I cut of the leading / of the path with a slice expression: path[1:] so we don't have to deal with an empty field name inside the loop.
The above getValue() returns the result as an interface{}. If you need the ID as an int, you may use type assertion like this:
var intID int
intID = id.(int)
Also note that it may be nicer / more useful to use a variadic parameter for the path:
func getValue(i interface{}, path ...string) interface{} {
v := reflect.ValueOf(i)
for _, field := range path {
v = v.FieldByName(field)
}
return v.Interface()
}
func main() {
mycat := cat{"cat", mcat{1}}
id := getValue(mycat, "M", "ID")
fmt.Println(id)
}
Output is the same. Try this one on the Go Playground.

Resources