Call dynamic types' interface method - go

I'm trying to write a common module using Go interface. It results in an error:
panic: interface conversion: interface {} is main.AmazonOrder, not main.OrderMapper
Code:
package main
type AmazonOrder struct {
OrderId string
ASIN string
}
func (o AmazonOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type EbayOrder struct {
OrderId string
SKU string
}
func (o EbayOrder) Generalise() *Order {
return &Order{
ChannelOrderId: o.OrderId,
}
}
type Order struct {
ID string
ChannelOrderId string
}
type OrderMapper struct {
Generalise func() *Order
}
var orderFactory = map[string]func() interface{} {
"amazon": func() interface{} {
return AmazonOrder{}
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId)
}
In plain sight logic, this should work fine. But, definitely I'm misunderstanding the type conversion in Go. TIA for clarifying what it is.
Playground: https://play.golang.org/p/tHCzKGzEloL

you have to use interface, not a struct with function field, i left comments around the ganges.
// OrderMapper is probably ment to be an interface. You can convert interface
// to struct only if its the original one. Though you need to convert it into iterface in
// your case because type of return value is variable. As all orders implement
// 'Generalise' and you are planing to convert empty interface to OderMapper why not just
// return order mapper right away? also best practice to name your interface, with just
// one method, is methodName + 'er' so Generalizer.
type OrderMapper interface {
Generalise() *Order
}
/* alternative
var orderFactory = map[string]func() OrderMapper{
"amazon": func() OrderMapper {
return AmazonOrder{OrderId: "amazonOrderID"}
},
"ebay": func() OrderMapper {
return EbayOrder{}
},
"vanilla": func() OrderMapper {
return Order{}
},
}
*/
var orderFactory = map[string]func() interface{}{
"amazon": func() interface{} {
return AmazonOrder{OrderId: "amazonOrderID"} // i added a value to verify correctness
},
"ebay": func() interface{} {
return EbayOrder{}
},
"vanilla": func() interface{} {
return Order{}
},
}
func main() {
orderType := "amazon"
initialiseOrder := orderFactory[orderType]
anOrder := initialiseOrder()
// Unmarshal from json into anOrder etc.. here.
// alternative: theOrder := anOrder.Generalise()
theOrder := anOrder.(OrderMapper).Generalise()
println(theOrder.ChannelOrderId == "amazonOrderID") // true
}

Related

Unmarshal YAML into complex object which may be either struct or string

Trying to unmarshal YAML into complex object such as map[string]map[interface{}]string.
The problem is that I want to be able to differentiate an interface{} part between string and Source which is a struct.
type Source struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
LogoID string `yaml:"logoId"`
URL string `yaml:"url"`
}
type UNFT struct {
ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
// could be
// ItemMeta map[string]map[string]string `yaml:"item_meta"`
// or
// ItemMeta map[string]map[Source]string `yaml:"item_meta"`
}
Obviously YAML does not know how to unmarshal into Source struct so I have to implement Unmarshaler interface:
type Unmarshaler interface {
UnmarshalYAML(value *Node) error
}
But I don't quite understand the big picture of unmarshaling process. In general I assume that I have to manually traverse *yaml.Node and call func UnmarshalYAML(value *Node) error on every node.
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type Source struct {
ID string `json:"id"`
Name string `json:"name"`
LogoID string `json:"logoId"`
URL string `json:"url"`
}
var data = `
unf:
item_meta:
source:
!struct
? id: "data-watch"
name: "DataWatch"
logoid: "data-watch"
url: "https"
: "product_any('SS')"
public_usage:
"": "source_any('SDF')"
"provider": "source_any('ANO')"`
type UNFT struct {
ItemMeta map[string]map[interface{}]string `yaml:"item_meta"`
}
type MetaConverterConfigT struct {
UNFT UNFT `yaml:"unf"`
}
func main() {
cfg := MetaConverterConfigT{}
err := yaml.Unmarshal([]byte(data), &cfg)
if err != nil {
fmt.Println("%w", err)
}
fmt.Println(cfg)
}
func (s *UNFT) UnmarshalYAML(n *yaml.Node) error {
var cfg map[string]map[interface{}]string
if err := n.Decode(&cfg); err != nil {
fmt.Println("%w", err)
}
return nil
}
Go playground
type MetaKey struct {
String string
Source Source
}
func (k *MetaKey) UnmarshalYAML(n *yaml.Node) error {
if n.Tag == "!!str" {
return n.Decode(&k.String)
}
if n.Tag == "!!map" {
return n.Decode(&k.Source)
}
return fmt.Errorf("unsupported MetaKey type")
}
// ...
type UNFT struct {
ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}
https://go.dev/play/p/Nhtab4l-ANT
If you need the map type to remain as is, i.e. without adding the custom key type, then you can implement the unmarshaler on UNFT as well and just do a re-mapping with any:
type UNFT struct {
ItemMeta map[string]map[any]string `yaml:"item_meta"`
}
func (u *UNFT) UnmarshalYAML(n *yaml.Node) error {
var obj struct {
ItemMeta map[string]map[MetaKey]string `yaml:"item_meta"`
}
if err := n.Decode(&obj); err != nil {
return err
}
u.ItemMeta = make(map[string]map[any]string, len(obj.ItemMeta))
for k, v := range obj.ItemMeta {
m := make(map[any]string, len(v))
for k, v := range v {
if k.Source != (Source{}) {
m[k.Source] = v
} else {
m[k.String] = v
}
}
u.ItemMeta[k] = m
}
return nil
}
https://go.dev/play/p/uwboGKf3qnD

how to append different functions but the same interface in slice

I tried to append different functions that can be represented with the same interface.
Functions return different objects but same interface.
It failed with the error cannot use Test (value of type func() *Dog) as func() Animal value in argument to append (typecheck)
What should I do? Thanks in advance!
package main
type Dog struct {
Word string
}
type Cat struct {
Word string
}
func (d *Dog) Say() string {
return d.Word
}
func (c *Cat) Say() string {
return c.Word
}
type Animal interface {
Say() string
}
func main() {
funcs := []func() Animal{}
funcs = append(funcs, Test) // error| cannot use Test (value of type func() *Dog) as func() Animal value in argument to append (typecheck)
}
func Test() *Dog {
return &Dog{Word: "dog"}
}
func Test2() *Cat {
return &Cat{Word: "cat"}
}
Change your functions to have Animal as their return type. func() *Dog is not convertible to func() Animal, they are two separate data types.
Similar to how you can pass, say, int as interface{}, but not []int as []interface{}
The slice element and functions have different return types. Use anonymous functions to convert the function return values to the slice element return type.
funcs = append(funcs,
func() Animal { return Test() },
func() Animal { return Test2() })
for _, f := range funcs {
fmt.Println(f().Say())
}
Run it on the Playground.
Another option is to use the reflect package to call the function and convert the result to an Animal.
func makeAnimal(f interface{}) Animal {
// This function assumes that f is a function
// that returns a value that satisfies the
// Animal interface.
result := reflect.ValueOf(f).Call(nil)
return result[0].Interface().(Animal)
}
Use it like this:
funcs := []interface{}{}
funcs = append(funcs, Test, Test2)
for _, f := range funcs {
a := makeAnimal(f)
fmt.Println(a.Say())
}
Run it on the Playground.
The problem is that func () *Dog cannot converts to func() Animal.
If you don't wanna use reflection, you have to change the "funcs" type to []interface{} then cast each element of the slice into func() *Dog and simply call it, like this:
package main
import "fmt"
type Dog struct {
Word string
}
type Cat struct {
Word string
}
func (d *Dog) Say() string {
return d.Word
}
func (c *Cat) Say() string {
return c.Word
}
type Animal interface {
Say() string
}
func main() {
var funcs []interface{}
funcs = append(funcs, Test)
fmt.Println(funcs[0].(func() *Dog)().Say()) // prints "dog"
}
func Test() *Dog {
return &Dog{Word: "dog"}
}
func Test2() *Cat {
return &Cat{Word: "cat"}
}

Mock inner function in golang

I want to mock function using interface and I was able to mock the first function callsomething
With great help from icza , Now it’s a bit more tricky . I want to test function vl1
With mock for function function1 , how it can be done.
https://play.golang.org/p/w367IOjADFV
package main
import (
"fmt"
"time"
"testing"
)
type vInterface interface {
function1() bool
}
type mStruct struct {
info string
time time.Time
}
func (s *mStruct) function1() bool {
return true
}
func callSomething(si vInterface) bool {
return si.function1()
}
func (s *mStruct) vl1() bool {
return callSomething(s)
}
var currentVt1 mStruct
func main() {
vl1 := currentVt1.vl1()
fmt.Println(vl1)
}
//——————————————————TESTS——————————————————
// This test is working as expected (as suggested by icza) for the function "callSomething"
// here we use mock interface and mock function which helps to mock function1
type mockedVInterface struct {
value bool
}
func (m mockedVInterface) fn1() bool {
return m.value
}
func Test_callSomething(t *testing.T) {
type args struct {
si vInterface
}
tests := []struct {
name string
args args
want bool
}{
{
name: "first value",
args: args{
si: mockedVInterface{value: false},
},
want: false,
},
{
name: "second value",
args: args{
si: mockedVInterface{value: true},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := callSomething(tt.args.si); got != tt.want {
t.Errorf("callSomething() = %v, want %v", got, tt.want)
}
})
}
}
//----Here is the test which a bit tricky
//I want to call to "s.vl1()" and test it with mock for "function1"
func Test_mStruct_vl1(t *testing.T) {
type fields struct {
info string
time time.Time
}
tests := []struct {
name string
fields fields
want bool
}{
{
name: "test 2",
fields: struct {
info string
time time.Time
}{info: "myinfo", time: time.Now() },
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &mStruct{
info: tt.fields.info,
time: tt.fields.time,
}
//here the test is starting
if got := s.vl1(); got != tt.want {
t.Errorf("mStruct.vl1() = %v, want %v", got, tt.want)
}
})
}
}
If you want to mock a function in Go you'll have to declare a variable that will hold the function value, have all callers of the function use the variable instead and then during tests you can switch up the function value for a mock implementation.
func callSomethingFunc(si vInterface) bool {
return si.function1()
}
var callSomething = callSomethingFunc
func (s *mStruct) vl1() bool {
return callSomething(s)
}
// ...
func Test_mStruct_vl1(t *testing.T) {
callSomething = func(si vInterface) bool { // set mock
// do mock stuff
}
defer func(){
callSomething = callSomethingFunc // set back original func at end of test
}()
// ...

How to check if all fields of a *struct are nil?

I'm not quite sure how to address this question, please feel free to edit.
With the first code block below, I am able to check if a all fields of a struct are nil.
In reality however, the values injected in the struct, are received as args.Review (see second code block below).
In the second code block, how can I check if all fields from args.Review are nil?
Try it on Golang Playground
package main
import (
"fmt"
"reflect"
)
type review struct {
Stars *int32 `json:"stars" bson:"stars,omitempty" `
Commentary *string `json:"commentary" bson:"commentary,omitempty"`
}
func main() {
newReview := &review{
Stars: nil,
// Stars: func(i int32) *int32 { return &i }(5),
Commentary: nil,
// Commentary: func(i string) *string { return &i }("Great"),
}
if reflect.DeepEqual(review{}, *newReview) {
fmt.Println("Nothing")
} else {
fmt.Println("Hello")
}
}
Try the second code on Golang Playground
This code below gets two errors:
prog.go:32:14: type args is not an expression
prog.go:44:27: args.Review is not a type
package main
import (
"fmt"
"reflect"
)
type review struct {
Stars *int32 `json:"stars" bson:"stars,omitempty" `
Commentary *string `json:"commentary" bson:"commentary,omitempty"`
}
type reviewInput struct {
Stars *int32
Commentary *string
}
type args struct {
PostSlug string
Review *reviewInput
}
func main() {
f := &args {
PostSlug: "second-post",
Review: &reviewInput{
Stars: func(i int32) *int32 { return &i }(5),
Commentary: func(i string) *string { return &i }("Great"),
},
}
createReview(args)
}
func createReview(args *struct {
PostSlug string
Review *reviewInput
}) {
g := &review{
Stars: args.Review.Stars,
Commentary: args.Review.Commentary,
}
if reflect.DeepEqual(args.Review{}, nil) {
fmt.Println("Nothing")
} else {
fmt.Println("Something")
}
}
If you're dealing with a small number of fields you should use simple if statements to determine whether they are nil or not.
if args.Stars == nil && args.Commentary == nil {
// ...
}
If you're dealing with more fields than you would like to manually spell out in if statements you could use a simple helper function that takes a variadic number of interface{} arguments. Just keep in mind that there is this: Check for nil and nil interface in Go
func AllNil(vv ...interface{}) bool {
for _, v := range vv {
if v == nil {
continue
}
if rv := reflect.ValueOf(v); !rv.IsNil() {
return false
}
}
return true
}
if AllNil(args.Stars, args.Commentary, args.Foo, args.Bar, args.Baz) {
// ...
}
Or you can use the reflect package to do your bidding.
func NilFields(x interface{}) bool {
rv := reflect.ValueOf(args)
rv = rv.Elem()
for i := 0; i < rv.NumField(); i++ {
if f := rv.Field(i); f.IsValid() && !f.IsNil() {
return false
}
}
return true
}
if NilFields(args) {
// ...
}

Golang polymorphic parameters and returns

Say I have functions:
func ToModelList(cats *[]*Cat) *[]*CatModel {
list := *cats
newModelList := []*CatModel{}
for i := range list {
obj := obj[i]
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
func ToModelList(dogs *[]*Dog) *[]*DogModel {
list := *dogs
newModelList := []*DogModel{}
for i := range list {
obj := obj[i]
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
Is there a way to combine those two so I can do something like
func ToModelList(objs *[]*interface{}) *[]*interface{} {
list := *objs
// figure out what type struct type objs/list are
newModelList := []*interface{}
// type cast newModelList to the correct array struct type
for i := range list {
obj := obj[i]
// type cast obj based on objs's type
newModelList = append(newModelList, obj.ToModel())
}
return &newModelList
}
First, slices are already a reference, unless you need to change the slice itself, you do not need to pass it as a pointer.
Second, an interface{} can be regardless an object or a pointer to an object. You do not need to have *interface{}.
I am not sure what you are trying to achieve but you could do something like this:
package main
// Interface for Cat, Dog
type Object interface {
ToModel() Model
}
// Interface for CatModel, DogModel
type Model interface {
Name() string
}
type Cat struct {
name string
}
func (c *Cat) ToModel() Model {
return &CatModel{
cat: c,
}
}
type CatModel struct {
cat *Cat
}
func (c *CatModel) Name() string {
return c.cat.name
}
type Dog struct {
name string
}
func (d *Dog) ToModel() Model {
return &DogModel{
dog: d,
}
}
type DogModel struct {
dog *Dog
}
func (d *DogModel) Name() string {
return d.dog.name
}
func ToModelList(objs []Object) []Model {
newModelList := []Model{}
for _, obj := range objs {
newModelList = append(newModelList, obj.ToModel())
}
return newModelList
}
func main() {
cats := []Object{
&Cat{name: "felix"},
&Cat{name: "leo"},
&Dog{name: "octave"},
}
modelList := ToModelList(cats)
for _, model := range modelList {
println(model.Name())
}
}
You define interfaces for your Cat, Dogs etc and for your Model. Then you implement them as you want and it is pretty straight forward to do ToModelList().
you can make *CatModel and *DogModel both implement type PetModel {} interface, and just return []Pet in function signature.
func (cats []*Cat) []PetModel {
...
return []*CatModel {...}
}
func (dogs []*Dog) []PetModel {
...
return []*DogModel {...}
}
BTW: return a pointer of a slice in golang is useless.
If you strip away redundant assignments, and unnecessary pointers-to-slices, you'll find you have little code left, and duplicating it for each of your model types doesn't look so bad.
func CatsToCatModels(cats []*Cat) []*CatModel {
var result []*CatModel
for _, cat := range cats {
result = append(result, cat.ToModel())
}
return result
}
Unless this code is used in a lot of places I'd also consider just inlining it, since it's trivial code and only 4 lines when inlined.
Yes, you can replace all the types with interface{} and make the code generic, but I don't think it's a good tradeoff here.

Resources