如何将 JSON 反序列化为使用反射创建的值?

huangapple go评论85阅读模式
英文:

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.Requestc.Responsehttp.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"

这个错误信息很明显,说明你的itemreflect.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.

huangapple
  • 本文由 发表于 2017年4月8日 14:33:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/43290970.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定