简体   繁体   中英

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) {
    //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?

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.

修改最后一行代码:将user := inter.(models.User)改为user := inter.Interface().(models.User) ,试试看!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM