I’m working on a rest api but for some reason can't type assert an interface{} to its underlying type - int.
I send data via post request, to create an ad. It looks like so:
POST http://localhost:8080/api/ads
{
"Title": "Example Title",
"Section": "machinery",
"CostPerDayInCent": 34500,
"Description": "A description",
"User": 4,
"AllowMobileContact": true,
"AllowEmailContact": true,
"IsActive": false
}
The passed values are decoded into a map[string]interface{} like so:
var adToValidate map[string]interface{}
err = json.NewDecoder(r.Body).Decode(&adToValidate)
if err != nil {
api.errorLog.Printf("Error decoding ad object: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
My problem happens when I type assert the costperdayincent and user interface values to int.
localScopeAd.CostPerDayInCent, ok = adMapToValidate[“CostPerDayInCent”].(int)
if !ok {
fieldErrors[structFieldName] = fmt.Sprintf("%v value is not of int type", structFieldName)
}
The if statement executes, indicating it cant type assert. Why is this? Is it because the passed json treats every value as a string? How would I resolve this issue?
FOLLOW UP
I resolved this issue with the help of @DanielFarrell 's answer.
As I couldn't fit a response in the comment section, below is my reason for decoding into a map and not a struct:
I understand it would make a lot more sense to decode into a struct.
I had initially been decoding into a struct. However I had run into some issues when trying to validate the bool values. i.e "AllowMobileContact": true, "AllowEmailContact": true, "IsActive": false
If a user was to make a post request to create an ad leaving out the above values. When the request body was decoded the above fields would default to false in the struct (bools 0 value). If i was then going to validate the passed in values I wouldn't know if the user had entered false or had left out a whole key value pair.
As I wanted to ensure the user had entered these values I first decoded into a map so that I could check if the key of the bool key value pair was present. Then I could send the relevant response if there was some missing required bool data.
If you know of a simpler way of going about the above I'd be interested in hearing it. There's some 3rd party packages that have 3 value boolean types which may have worked but I decided to go with the above instead.
I would prefer to decode into a struct and let json/encoding
do the work of handling proper type.
type Input struct {
Title,
Selection string
CostPerDayInCent int64
Description string
User int64
AllowMobileContact,
AllowEmailContact,
IsActive bool
}
This approach is quite common and useful for several reasons. First, you get to choose the type. Second, you are defining a type, so you get to attach functions, which I find quite convenient as a method to organize my code and avoid having to pass structures as explicit function arguments. Third, you can attach annotations to the struct's fields, further adjusting unmarshalling.
Finally, and to me, most importantly, you're thinking about data the way Go handles it. In many of the popular modern languages such as Python, the code - and the documentation - doesn't usually attach types to functions, or attempt to explicitly enumerate attributes of objects. Go is different. Its removal of inherent language reflection is one of the reasons it is so performant. This can provide vast benefits to the programmer - when you know all the types, you know exactly how things will behave, and can precisely identify what you must pass the functions you call. I suggest you embrace the explicit typing, and avoid decoding json into interface-oriented types whenever possible. You'll know what you're handling, and you'll be able to define it explicitly for your consumers as well.
https://play.golang.org/p/egaequIT8ET contrasts your approach, in which you don't need to bother defining the types - but you also don't have the opportunity to choose them.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type Input struct {
Title,
Selection string
CostPerDayInCent int64
Description string
User int64
AllowMobileContact,
AllowEmailContact,
IsActive bool
}
func main() {
var input = `{
"Title": "Example Title",
"Section": "machinery",
"CostPerDayInCent": 34500,
"Description": "A description",
"User": 4,
"AllowMobileContact": true,
"AllowEmailContact": true,
"IsActive": false
}`
// map[string]interface
data := make(map[string]interface{})
if err := json.NewDecoder(bytes.NewBufferString(input)).Decode(&data); err != nil {
panic(err)
}
fmt.Printf("%f %T\n", data["CostPerDayInCent"], data["CostPerDayInCent"])
// structure
obj := new(Input)
if err := json.NewDecoder(bytes.NewBufferString(input)).Decode(obj); err != nil {
panic(err)
}
fmt.Printf("%d %T", obj.CostPerDayInCent, obj.CostPerDayInCent)
}