golang how to access promoted type - go

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

Related

How to put various structs in a list and manipulate?

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

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

Copying field/value from src to dest object

I am trying to copy the fields from one struct value to another, where they have the same field definitions. I have this program:
package main
import (
"log"
"reflect"
)
func setExistingFields(src interface{}, dst interface{}) {
fields := reflect.TypeOf(src)
values := reflect.ValueOf(src)
num := fields.NumField()
s := reflect.ValueOf(src).Elem()
d := reflect.ValueOf(dst).Elem()
for i := 0; i < num; i++ {
field := fields.Field(i)
value := values.Field(i)
fsrc := s.FieldByName(field.Name)
fdest := d.FieldByName(field.Name)
if fdest.IsValid() && fsrc.IsValid() {
if fdest.CanSet() && fsrc.CanSet() {
fdest.Set(value)
}
}
}
}
// and then we main:
func main() {
src := struct {
Foo string
Bar string
}{
"dog",
"pony",
}
dest := struct{ Foo string; Bar string }{}
setExistingFields(&src, &dest)
log.Println("dest.Foo", dest.Foo)
}
I run that, but I get an error:
reflect: NumField of non-struct type
I can't figure out what that's about.
Here's a playground link:
https://play.golang.org/p/TsHTfAaeKhc
Try this out:
func setExistingFields(src interface{}, dst interface{}) {
srcFields := reflect.TypeOf(src).Elem()
srcValues := reflect.ValueOf(src).Elem()
dstValues := reflect.ValueOf(dst).Elem()
for i := 0; i < srcFields.NumField(); i++ {
srcField := srcFields.Field(i)
srcValue := srcValues.Field(i)
dstValue := dstValues.FieldByName(srcField.Name)
if dstValue.IsValid() {
if dstValue.CanSet() {
dstValue.Set(srcValue)
}
}
}
}
Note that you need to do additional checking if src field value is assignable to dst field type.
Edit: The reason why you are getting that error is because fields at that point is a pointer to a struct. You need to get the actual struct value by using Elem().
This won't work: A struct always gets its "schema" (eg. its fields) during compile time... You cannot add more fields during runtime.
I don't see what your exact use case is, but consider something like map[string]string or even map[string]interface{} to be able to "extend" the content/fields of the thing you are passing around...

Resources