Reputation: 657
package controllers
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"reflect"
)
func GetTypeFromReq(c *App, ty interface{}) (interface{}, error) {
//get the type we are going to marshall into
item := reflect.ValueOf(ty)
//define and set the error that we will be returning to null
var retErr error
retErr = nil
//extract the body from the request and defer closing of the body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
//handle errors and unmarshal our data
if err != nil {
retErr = errors.New("Failed to Read body: " + err.Error())
} else if err = json.Unmarshal(body, &item); err != nil {
retErr = errors.New("Unmarshal Failed: " + err.Error())
}
return item, retErr
}
I am trying to pass a type and a request into a function, then inside that function unMarshall the request into a variable and return it.
I assume my approach is wrong because when i try to do this:
inter, err := GetTypeFromReq(&c, models.User{})
if err != nil {
revel.ERROR.Println(err.Error())
}
user := inter.(models.User)
I get the error "interface conversion: interface {} is reflect.Value, not models.User"
any tips on how to approach this?
Upvotes: 3
Views: 1775
Reputation: 120969
Here's how to modify the the function to make it work as expected:
func GetTypeFromReq(c *App, ty interface{}) (interface{}, error) {
// Allocate new value with same type as ty
v := reflect.New(reflect.TypeOf(ty))
//define and set the error that we will be returning to null
var retErr error
retErr = nil
//extract the body from the request and defer closing of the body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
//handle errors and unmarshal our data
if err != nil {
retErr = errors.New("Failed to Read body: " + err.Error())
} else if err = json.Unmarshal(body, v.Interface()); err != nil {
retErr = errors.New("Unmarshal Failed: " + err.Error())
}
// v holds a pointer, call Elem() to get the value.
return v.Elem().Interface(), retErr
}
Note the calls to Interface() to get a reflect.Value
's current value.
Here's an approach that avoids reflection and type assertions:
func GetFromReq(c *App, item interface{}) error {
//extract the body from the request and defer closing of the body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
//handle errors and unmarshal our data
if err != nil {
retErr = errors.New("Failed to Read body: " + err.Error())
} else if err = json.Unmarshal(body, item); err != nil {
retErr = errors.New("Unmarshal Failed: " + err.Error())
}
return retErr
}
Use it like this:
var user models.User
err := GetFromReq(&c, &user)
if err != nil {
revel.ERROR.Println(err.Error())
}
Use a JSON decoder to simplify the code:
func GetFromReq(c *App, item interface{}) error {
defer c.Request.Body.Close()
return json.NewDecoder(io.LimitReader(c.Request.Body, 1048576)).Deocode(item)
}
If c.Request
is a *http.Request
and c.Response
is an http.ResponseWriter
, then write the function as:
func GetFromReq(c *App, item interface{}) error {
return json.NewDecoder(http.MaxBytesReaer(c.Response, c.Request.Body, 1048576)).Deocode(item)
}
There's no need to close the request body in the net/http server. Use MaxBytesReader instead of io.LimitReader to prevents clients from accidentally or maliciously sending a large request and wasting server resources.
Upvotes: 2
Reputation: 10564
"interface conversion: interface {} is reflect.Value, not models.User"
pretty straight forward about the message error. That your item
is reflect.Value
it is not models.User
.
so I think in your code you can change the item
to models.User
.
But I assume that your are tying to create the function that will work with all type of your models, in this case models.User{}
.
Your approach is expensive since it is using interface
. you could convert the incoming request
directly like this:
func GetTypeFromReq(c *App, ty models.User) (models.User, error) {
//get the type we are going to marshall into
var item models.User
//define and set the error that we will be returning to nil
var retErr error // this var if the value not define then it is nil. Because error is interface
//extract the body from the request and defer closing of the body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
//handle errors and unmarshal our data
if err != nil {
retErr = errors.New("Failed to Read body: " + err.Error())
} else if err = json.Unmarshal(body, &item); err != nil {
retErr = errors.New("Unmarshal Failed: " + err.Error())
}
return item, retErr
}
if your body
has the same structure as your model it will give you the value, if not then it is error
.
Note that you need to be careful when using interface
. you can see some guideline in this article. Use an interface:
Your function convert the value of your models.User
to interface
, and then return the interface
value. that's why it's expensive.
Upvotes: 0
Reputation: 853
Modify code of the last line: change user := inter.(models.User)
to user := inter.Interface().(models.User)
,have a try!
Upvotes: 0