I'm developing a tool that can be implemented to simplify the process of creating simple CRUD operations/endpoints. Since my endpoints don't know what kind of struct they'll be receiving, I've created an interface that users can implement, and return an empty object to be filled.
type ItemFactory interface {
GenerateEmptyItem() interface{}
}
And the users would implement something like:
type Test struct {
TestString string `json:"testString"`
TestInt int `json:"testInt"`
TestBool bool `json:"testBool"`
}
func (t Test) GenerateEmptyItem() interface{} {
return Test{}
}
When the Test object gets created, its type is "Test", even though the func returned an interface{}. However, as soon as I try to unmarshal some json of the same format into it, it strips it of its type, and becomes of type "map[string]interface {}".
item := h.ItemFactory.GenerateEmptyItem()
//Prints "Test"
fmt.Printf("%T\n", item)
fmt.Println(reflect.TypeOf(item))
err := ConvertRequestBodyIntoObject(r, &item)
if err != nil {...}
//Prints "map[string]interface {}"
fmt.Printf("%T\n", item)
Func that unmarshalls item:
func ConvertRequestBodyIntoObject(request *http.Request, object interface{}) error {
body, err := ioutil.ReadAll(request.Body)
if err != nil {
return err
}
// Unmarshal body into request object
err = json.Unmarshal(body, object)
if err != nil {
return err
}
return nil
}
Any suggestions as to why this happens, or how I can work around it?
Thanks
Your question lacks an example showing this behavior so I'm just guessing this is what is happening.
func Generate() interface{} {
return Test{}
}
func GeneratePointer() interface{} {
return &Test{}
}
func main() {
vi := Generate()
json.Unmarshal([]byte(`{}`), &vi)
fmt.Printf("Generate Type: %T\n", vi)
vp := GeneratePointer()
json.Unmarshal([]byte(`{}`), vp)
fmt.Printf("GenerateP Type: %T\n", vp)
}
Which outputs:
Generate Type: map[string]interface {}
GenerateP Type: *main.Test
I suggest you return a pointer in GenerateEmptyItem() instead of the actual struct value which is demonstrated in the GenerateP() example.
Playground Example
Related
I have the concept of Context which is a map that can hold any structure. Basically, I want to create a generic getter that adddressably 'populates' the destination interface (similarly to how json decoding works).
Here's an example of how I want this to work:
type Context map[string]interface{}
// Random struct that will be saved in the context
type Step struct {
Name string
}
func main() {
stepA := &Step{Name: "Cool Name"}
c := Context{}
c["stepA"] = stepA
var stepB *Step
err := c.Get("stepA", stepB)
if err != nil {
panic(err)
}
fmt.Println(stepB.Name) // Cool Name
stepB.Name = "CoolName2"
fmt.Println(stepA.Name) // I want to say: CoolName2
}
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
destinationValue := reflect.ValueOf(dest)
contextValue := reflect.ValueOf(context)
destinationValue.Set(contextValue) // Errors here
return nil
}
I leaned towards using reflect, but maybe I don't need it? - so opened to other suggestions (except for generics as that complicates other matters) I'm getting the following error with the above:
panic: reflect: reflect.Value.Set using unaddressable value
You can test it here.
The argument passed to Get must be a pointer type whose element type is identical to the type in the context map. So if the value in the context map is of type *Step, then the argument's type must be **Step. Also the passed in argument cannot be nil, it can be a pointer to nil, but it itself cannot be nil.
So in your case you should do:
var stepB *Step
err := c.Get("stepA", &stepB) // pass pointer-to-pointer
if err != nil {
panic(err)
}
And the Get method, fixed up a bit:
func (c Context) Get(stepId string, dest interface{}) error {
context, ok := c[stepId]
if !ok {
return nil
}
dv := reflect.ValueOf(dest)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return errors.New("dest must be non-nil pointer")
}
dv = dv.Elem()
cv := reflect.ValueOf(context)
if dv.Type() != cv.Type() {
return errors.New("dest type does not match context value type")
}
dv.Set(cv)
return nil
}
https://go.dev/play/p/OECttqp1aVg
I'm trying to seed my Postgres database as functionally. In my case, SeedSchema() function can take any type struct. So I define a interface and create functions to my structs which will seed. I tried with generics and without.
When I unmarshall any json array from file as byte array, json.Unmarshall method change my tempMember member of struct. Exp, models.Term to map[string]interface{}. I've used unmarshall before this function and I've not seen like this situation.
Here is my SeedSchema() function:
func (db *Database) SeedSchema(models ...globals.Seeder[any]) error {
var (
subjects []globals.Seeder[any]
fileByte []byte
err error
// tempMember any
)
if len(models) == 0 {
subjects = seederModelList
} else {
subjects = models
}
for _, model := range subjects {
fileName, tempMember := model.Seed()
fmt.Printf("%+v\n", reflect.TypeOf(tempMember)) //1
if fileByte, err = os.ReadFile("db/seeds/" + fileName); err != nil {
fmt.Println(err)
return err
}
if err = json.Unmarshal(fileByte, &tempMember); err != nil {
fmt.Println(err)
return err
}
fmt.Printf("%+v\n", reflect.TypeOf(tempMember)) //2
}
return nil
}
First print returns []models.AirportCodes and the second []interface {}.
Here is my interface and model:
func (AirportCodes) Seed() (string, any) {
return "airport_codes.json", []AirportCodes{}
}
type Seeder[T any] interface {
Seed() (string, T)
// Seed(*gorm.DB) error
TableName() string
}
seederModelList = []globals.Seeder[any]{
m.AirportCodes{},
m.Term{},
}
After a few weeks, I have looking for solve this problem and look unmarshaler interfaces and examples. Then Like what icza said, I started to look over the my code that convention between types and I solved like this. If you guys have better answer than mine, please add answer.
Data:
[
{
"id":1,
"name":"Term 1",
"content": [
"a1",
"a2",
"a3"
]
}
]
Result:
[{ID:1 Name:Term 1 Content:[a1 a2 a3]}]
UnmarshalJSON Function:
func (term *Term) UnmarshalJSON(data []byte) error {
tempMap := map[string]interface{}{}
if err := json.Unmarshal(data, &tempMap); err != nil {
return err
}
*term = Term{
Name: tempMap["name"].(string),
}
if tempMap["content"] != nil {
for _, v := range tempMap["content"].([]interface{}) {
(*term).Content = append((term).Content, v.(string))
}
}
return nil
}
Thank you for comments.
I have a set of functions, which uses the pool of objects. This pool has been mocked. It works fine in most of the cases. But in some functions i call the methods of objects from the pool. So i need to mock this objects too.
Lets say:
// ObjectGeter is a interface that is mocked
type ObjectGeter interface {
GetObject(id int) ObjectType, error
}
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
obj, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := obj.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
Is there a way to test whole this function? I can create interface SomeMethoder which wraps the SomeMethod(), but i can't find the way how to assign it to obj inside SomeFunc without changing the signature of GetObject to GetObject(id int) SomeMethoder,error.
Currently i see the one approach - testing by a parts.
The only solution i'v found without of changing of paradigm is a wrapper. It is pretty trivial but may be some one will need it once.
Originally i have some type:
type PoolType struct {...}
func (p *PoolType)GetObject(id int) (ObjectType, error) {...}
and interface, that wraps PoolType.GetObject and that i'v mocked.
Now i have the interface:
type SomeMethoder interface {
SomeMethod() (ResultType, error)
}
to wrap object returned by PoolType.GetObject().
To produce it i have interface:
type ObjectGeter interface {
GetObject(id int) (SomeMethoder, error)
}
and type
type MyObjectGeter struct {
pool *PoolType
}
func New(pool *PoolType) *MyObjectGeter {
return &MyObjectGeter{pool: pool}
}
func (p *MyObjectGeter)GetObject(id int) (SomeMethoder, error) {
return p.pool.GetObject(id)
}
that implements it.
So:
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
iface, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := iface.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
is called by
og := New(pool)
SomeFunc(og,id,otherArgument)
in real work.
After all to test whole SomeFunc i have to:
func TestSomeFuncSuccess (t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
objectGeter := mocks.NewMockObjectGeter(controller)
someMethoder := mocks.NewMockSomeMethoder(controller)
gomock.InOrder(
args.objectGeter.EXPECT().
GetObject(correctIdCOnst).
Return(someMethoder, nil),
args.someMethoder.EXPECT().
SomeMethod().
Return(NewResultType(...),nil).
Times(args.test.times[1]),
)
result, err := SomeFunc(objectGeter,correctIdCOnst,otherArgumentConst)
// some checks
}
So, the only untested part is MyObjectGeter.GetObject that is enough for me.
Is it possible to have my function definition below accept any type of struct?
I've tried to refactor like so:
// This method should accept any type of struct
// Once I receive my response from the database,
// I scan the rows to create a slice of type struct.
func generateResponse(rows *sqlx.Rows, structSlice []struct{}, structBody struct{}) ([]struct{}, error) {
for rows.Next() {
err := rows.StructScan(&structBody)
if err != nil {
return nil, err
}
structSlice = append(structSlice, structBody)
}
err := rows.Err()
if err != nil {
return nil, err
}
return structSlice, nil
}
Assume my struct is of type OrderRevenue.
When I call the function above:
structSlice, err := generateResponse(rows, []OrderRevenue{}, OrderRevenue{})
The error I get is:
cannot use []OrderRevenue literal as type []struct{} in argument...
Am I going about this the wrong way?
This is considered the cornerstone (or more of a limitation) of Go's type system. struct{} is an unnamed type that is different from struct{ field1 int } and of course is not the same as OrderRevenue{}.
Go emphasizes abstraction through interfaces, and perhaps you should try that. Here is the first take:
type OrderRevenue interface {
MarshalMyself() ([]byte, error)
}
type Anonymous struct {}
func (a Anonymous) MarshalMyself() ([]byte, error) {
// implementation's up to you
return []byte{}, nil
}
// the function signature
generateResponse(rows *sqlx.Rows, structSlice []OrderRevenue, structBody Body) ([]Body, error) {
// ...
}
In this case you can also use empty interface interface{}, which all types implement, but you'll have to recursively go through the structure to do manual type assertion. The best approach in Go is to know the shape of your data in advance, at least partially.
I am parsing a form and have written a number of functions func parseAndValidateX(val string) (T, err) where T is any type.
Now I would like to write a closure func catchError(T, Error) T, so that I can do something like:
errors []Error
func catchError(val T, err Error) T {
if err != nil {
//append err to errors
}
return val
}
data = MyStruct {
Age = catchError(parseAndValidateAge("5")) // Age is a int
DistanceFromHome = catchError(parseAndValidatePi("3.14")) // DistanceFromHome is a float
Location = catchError(parseAndValidatePi("3.14,2.0")) //Location is a custom Struct
}
if len(errors) > 0 {
// o, oh
}
Is this possible in Go? How can this be done easily/idiomatically?
Nope; you cannot do that since Go has no parametric polymorphism for user-defined functions. All you can do is take and return interface{} and add type assertions at the call sites.
Everything in Go is type-safe if you don't use the unsafe package, so you don't have to worry about that. A type assertion will fail at runtime instead of at compile-time, though.
If you are willing to violate DRY, though:
type errorList []Error
func (l *errorList) addIfNotNil(err Error) {
if err != nil {
*l = append(*l, err)
}
}
func (l *errorList) int(x int, err Error) int {
l.addIfNotNil(err)
return x
}
func (l *errorList) float32(x float32, err Error) float32 {
l.addIfNotNil(err)
return x
}
list := errorList([]Error{})
data := MyStruct{
Age: list.int(parseAndValidateAge("5")),
DistanceFromHome: list.float32(parseAndValidatePi("3.14")),
}