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
}
Related
I have a lot (20+) "Enum Types" of the following form. What I'd like to do is list all the Enum types by name in a struct (see below) and a pointer to a function that returns all of the types. See below for an abbreviated sample.
package main
type Fruit int
const (
unknownFruit Fruit = iota // must be first
Apple
Banana
fruitDone
)
func FruitTypes() []Fruit {
var res []Fruit
for typ := unknownFruit + 1; typ < fruitDone; typ++ {
res = append(res, typ)
}
return res
}
type Car int
const (
unknownCar Car = iota // must be first
BMW
Mercedes
doneCar
)
func CarTypes() []Car {
var res []Car
for typ := unknownCar + 1; typ < doneCar; typ++ {
res = append(res, typ)
}
return res
}
func main() {
enumTypes := []struct {
Name string
Length int
ConvertFunc func(int) string
}{
{Name: "Fruit", Length: int(doneFruit), ConvertFunc: ConvertEnum[Fruit]},
{Name: "Car", Length: int(doneCar), ConvertFunc: ConvertEnum[Car]},
}
for _, enumType := range enumTypes {
fmt.Println(enumType.Name)
for i := 0; i < enumType.Length; i++ {
fmt.Printf(" -- %s", enumType.ConvertFunc(i))
}
}
}
func ConvertEnum[T any](raw int) string {
return fmt.Sprintf("%v", T(raw)) // #2 - This will not convert
}
Unfortunately, I cannot seem to declare the function pointer properly. What's the correct return type?
(As an aside, is there a way to declare an interface for these enum types and/or use generics so that I don't have to have FruitType(), CarType(), PetType()...
=================
UPDATE - thanks to #Woody1193, I feel like I'm much closer. I'm now using the fact that enums are sequential numbers, so I don't really need to pass in any details other than the type so I can cast it back inside the loop (see marker #1 above).
However, in the above, I get:
./main.go:55:64: cannot use EnumType[Fruit] (value of type func(done Fruit) []Fruit) as type func() []int in struct literal
./main.go:56:60: cannot use EnumType[Car] (value of type func(done Car) []Car) as type func() []int in struct literal
./main.go:62:43: too many arguments in call to typ.TypeFunction
=================
Update #2 - I've tried to take a different angle, and just pass in the conversion function, to get it out of the struct. (See above). But now it won't let me cast it :(
./main.go:68:31: cannot convert raw (variable of type int) to type T
The issue you're having here is that neither Car nor Fruit is actually an integer, so TypesFunction will not accept CarTypes or FruitTypes. Generics will not work in this situation because declaring a slice of such objects would require them to all have the same type parameter, which means your code still wouldn't work.
Therefore, your best option is to modify CarTypes and FruitTypes so that they have the same signature, func() []int:
func FruitTypes() []int {
var res []int
for typ := unknownFruit + 1; typ < fruitDone; typ++ {
res = append(res, typ)
}
return res
}
func CarTypes() []int {
var res []int
for typ := unknownCar + 1; typ < doneCar; typ++ {
res = append(res, typ)
}
return res
}
Of course, this will mean that you'll have to use type casting when you want the specific type of enum, but the code will work.
As an aside, you can use generics to generate the function itself:
func EnumType[T ~int](done T) []int {
var res []int
for typ := T(1); typ < done; typ++ {
res = append(res, typ)
}
return res
}
func ConvertEnum[T ~int](raw ...int) []T {
res := make([]T, len(raw))
for i, typ := range raw {
res[i] = T(typ)
}
return res
}
enumTypes := []struct {
Name string
TypesFunction func() []int
}{
{Name: "Fruit", TypesFunction: EnumType[Fruit]},
{Name: "Car", TypesFunction: EnumType[Car]},
}
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
I'm working on one of our system applications, specifically in the configuration file handling bits. We currently have 3 different places where a configuration file can be stored, and that can possibly be extended later. What I'm trying to do is simplify the way we need to add a new managed field.
The solution I have so far looks something like this:
package main
import (
"reflect"
"strconv"
"strings"
)
type Datastore interface{}
type MyInt struct {
intVal int
}
func NewMyInt(key string, dv int, db *Datastore) *MyInt {
// Do something here to construct MyInt
return &MyInt{intVal: dv}
}
type Config struct {
myInts map[string]*MyInt
// Tag is of form "<key in DB>:<default value>"
Value1 MyInt "value1_key:12345"
Value2 MyInt "value2_key:54321"
}
func NewConfig(db *Datastore) *Config {
c := &Config{
myInts: make(map[string]*MyInt),
}
cType := reflect.TypeOf(c)
for i := 0; i < cType.NumField(); i++ {
f := cType.Field(i)
if f.Name == "myInts" {
continue
}
tag := string(f.Tag)
fields := strings.Split(tag, ":")
switch f.Type.Name() {
case "myInt":
intVal, _ := strconv.Atoi(fields[1])
val := NewMyInt(fields[0], intVal, db)
c.myInts[fields[0]] = val
// How do I set the i'th field to this newly constructed value?
}
}
return c
}
So far I'm just missing this piece to do the assignment.
For this question, you can try
func NewConfig(db *Datastore) *Config {
c := &Config{
myInts: make(map[string]*MyInt),
}
cType := reflect.TypeOf(c).Elem() // have to use Elem() to get actual value
cValue := reflect.ValueOf(c).Elem()
for i := 0; i < cType.NumField(); i++ {
f := cType.Field(i)
if f.Name == "myInts" {
continue
}
tag := string(f.Tag)
fields := strings.Split(tag, ":")
switch f.Type.Name() {
case "MyInt":
intVal, _ := strconv.Atoi(fields[1])
val := NewMyInt(fields[0], intVal, db)
c.myInts[fields[0]] = val
// How do I set the i'th field to this newly constructed value?
cValue.Field(i).Set(reflect.ValueOf(val).Elem())
}
}
fmt.Println(c.Value1.intVal, c.Value2.intVal)
return c
}
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.