Sammy Roberts
Sammy Roberts

Reputation: 657

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?

Upvotes: 3

Views: 1775

Answers (3)

Thundercat
Thundercat

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

Gujarat Santana
Gujarat Santana

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:

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

Upvotes: 0

nick
nick

Reputation: 853

Modify code of the last line: change user := inter.(models.User) to user := inter.Interface().(models.User),have a try!

Upvotes: 0

Related Questions