英文:
How to unmarshall JSON into a value created with reflection?
问题
package controllers
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"reflect"
)
func GetTypeFromReq(c *App, ty interface{}) (interface{}, error) {
// 获取要转换的类型
item := reflect.ValueOf(ty)
// 定义并将要返回的错误设置为nil
var retErr error
retErr = nil
// 从请求中提取body并延迟关闭body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
// 处理错误并解析数据
if err != nil {
retErr = errors.New("读取body失败: " + err.Error())
} else if err = json.Unmarshal(body, &item); err != nil {
retErr = errors.New("解析失败: " + err.Error())
}
return item, retErr
}
我试图将一个类型和一个请求传递给一个函数,然后在该函数内部将请求解析为一个变量并返回它。
我猜我的方法是错误的,因为当我尝试这样做时:
inter, err := GetTypeFromReq(&c, models.User{})
if err != nil {
revel.ERROR.Println(err.Error())
}
user := inter.(models.User)
我得到错误信息 "interface conversion: interface {} is reflect.Value, not models.User"。
对于如何解决这个问题,你有什么建议吗?
英文:
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?
答案1
得分: 2
以下是修改函数使其按预期工作的方法:
func GetTypeFromReq(c *App, ty interface{}) (interface{}, error) {
// 为 ty 分配与其类型相同的新值
v := reflect.New(reflect.TypeOf(ty))
// 定义并将要返回的错误设置为 null
var retErr error
retErr = nil
// 从请求中提取 body 并延迟关闭 body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
// 处理错误并解组我们的数据
if err != nil {
retErr = errors.New("读取 body 失败: " + err.Error())
} else if err = json.Unmarshal(body, v.Interface()); err != nil {
retErr = errors.New("解组失败: " + err.Error())
}
// v 持有一个指针,调用 Elem() 获取值。
return v.Elem().Interface(), retErr
}
注意调用 Interface() 来获取 reflect.Value
的当前值。
以下是避免使用反射和类型断言的方法:
func GetFromReq(c *App, item interface{}) error {
// 从请求中提取 body 并延迟关闭 body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
// 处理错误并解组我们的数据
if err != nil {
return errors.New("读取 body 失败: " + err.Error())
} else if err = json.Unmarshal(body, item); err != nil {
return errors.New("解组失败: " + err.Error())
}
return nil
}
使用方法如下:
var user models.User
err := GetFromReq(&c, &user)
if err != nil {
revel.ERROR.Println(err.Error())
}
使用 JSON 解码器 可以简化代码:
func GetFromReq(c *App, item interface{}) error {
defer c.Request.Body.Close()
return json.NewDecoder(io.LimitReader(c.Request.Body, 1048576)).Decode(item)
}
如果 c.Request
是 *http.Request
,c.Response
是 http.ResponseWriter
,则将函数写为:
func GetFromReq(c *App, item interface{}) error {
return json.NewDecoder(http.MaxBytesReader(c.Response, c.Request.Body, 1048576)).Decode(item)
}
在 net/http 服务器中不需要关闭请求体。使用 MaxBytesReader 代替 io.LimitReader 可以防止客户端意外或恶意发送大型请求并浪费服务器资源。
英文:
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.
答案2
得分: 0
将最后一行代码修改为:将user := inter.(models.User)
改为user := inter.Interface().(models.User)
,试试看!
英文:
Modify code of the last line: change user := inter.(models.User)
to user := inter.Interface().(models.User)
,have a try!
答案3
得分: 0
"interface conversion: interface {} is reflect.Value, not models.User"
这个错误信息很明显,说明你的item
是reflect.Value
类型,而不是models.User
类型。
所以我认为你可以在代码中将item
改为models.User
类型。
但是我猜你可能是想创建一个可以适用于所有模型类型的函数,比如models.User{}
。
你的方法很耗费资源,因为它使用了interface
。你可以直接将incoming request
转换为如下形式:
func GetTypeFromReq(c *App, ty models.User) (models.User, error) {
// 获取我们要解析的类型
var item models.User
// 定义并将要返回的错误设置为nil
var retErr error // 如果该变量未定义值,则为nil。因为error是interface
// 从请求中提取body并延迟关闭body
body, err := ioutil.ReadAll(io.LimitReader(c.Request.Body, 1048576))
defer c.Request.Body.Close()
// 处理错误并解析我们的数据
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
}
如果你的body
与你的模型具有相同的结构,它将给你返回值,否则将返回错误。
请注意,在使用interface
时需要小心。你可以在这篇文章中看到一些指南。使用接口的情况包括:
- 当API的用户需要提供实现细节时。
- 当API需要在内部维护多个实现时。
- 当已经确定并需要解耦的API的某些部分可能会发生变化时。
你的函数将models.User
的值转换为interface
,然后返回该interface
值,这就是为什么它很耗费资源的原因。
英文:
> "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:
- When users of the API need to provide an implementation detail.
- When API’s have multiple implementations they need to maintain internally.
- When parts of the API that can change have been identified and require decoupling.
Your function convert the value of your models.User
to interface
, and then return the interface
value. that's why it's expensive.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论