json: cannot unmarshal string into Go struct field [duplicate] - go

This question already has an answer here:
cannot unmarshal string into Go struct field
(1 answer)
Closed 2 years ago.
I want to create a CRUD rest API to manage casts using Gorm and Gin. When I add a relation between two of my models, I cannot create a cast because of that converting the string ID to a struct type.
My Models:
type Cast struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
FullName string `gorm:"size:150;not null" json:"full_name"`
NickNames string `gorm:"size:250;null;" json:"nick_names"`
BornLocation Country `gorm:"many2many:CastBornLocation;association_foreignkey:ID;foreignkey:ID" json:"born_location"`
Nationalities []Country `gorm:"many2many:Castnationalities;association_foreignkey:ID;foreignkey:ID" json:"cast_nationalities"`
MiniBio string `gorm:"size:1000;null;" json:"mini_bio"`
}
type Country struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
Title string `gorm:"size:100;not null" json:"title"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
And here is the controller:
func (server *Server) CreateCast(c *gin.Context) {
errList = map[string]string{}
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
errList["Invalid_body"] = "Unable to get request"
c.JSON(http.StatusUnprocessableEntity, gin.H{
"status": http.StatusUnprocessableEntity,
"error": errList,
})
return
}
item := models.Cast{}
err = json.Unmarshal(body, &item)
if err != nil {
fmt.Println("---------------------------------")
fmt.Println(err)
fmt.Println("---------------------------------")
errList["Unmarshal_error"] = "Cannot unmarshal body"
c.JSON(http.StatusUnprocessableEntity, gin.H{
"status": http.StatusUnprocessableEntity,
"error": errList,
})
return
}
...
And this is the JSON body I'm submitting to the API:
{
"full_name": "Cast fullname",
"nick_names": "nickname1, nickname2",
"born_location": "01346a2e-ae50-45aa-8b3e-a66748a76955",
"Nationalities": [
"c370aa49-b39d-4797-a096-094b84903f27",
"01346a2e-ae50-45aa-8b3e-a66748a76955"
],
"mini_bio": "this is the mini bio of the cast"
}
And this is the full printed error:
json: cannot unmarshal string into Go struct field Cast.born_location of type models.Country

You can not unmarshal string into Struct type. BornLocation is country struct type but you are sending the only id of string type in JSON. Same for Nationalities. Try to send id in id node inside of the object to map with your struct.
{
"full_name": "Cast fullname",
"nick_names": "nickname1, nickname2",
"born_location": {
"id" :"01346a2e-ae50-45aa-8b3e-a66748a76955"
}
"Nationalities": [
{
"id" :"c370aa49-b39d-4797-a096-094b84903f27"
},
{
"id" :"01346a2e-ae50-45aa-8b3e-a66748a76955"
}
],
"mini_bio": "this is the mini bio of the cast"
}
Or create another struct for your request body to map your current JSON.

Related

Using validator to check if value is boolean

I'm new to Go, so this might be very easy, but I can't find it. I have an entity Page with these two properties:
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
Now, if you don't send a title we get a (not very pretty, but acceptable*):
Key: 'Page.Title' Error:Field validation for 'Title' failed on the 'required' tag.
Now, if I send this to the endpoint:
{
"title": "I'm a valid title",
"active": "I'm not a boolean at all!"
}
We get this:
json: cannot unmarshal string into Go struct field Page.Active of type bool
Which, IMO, is giving way too much info. What is the standard practice in Go to validate user input?
I was first making a page-validor.go with some checks, then I found this, but I'm not sure what is good practice in Go.
How do I validate this properly? Should I find check what is provided and then try to move it into the struct and validate the actual contents?
I am using GinGonic
* I've found a way to unwrap the errors and make it nicer
Write custom JSON Unmarshaller method for the type Page and inside UnmarshalJSON(bytes []byte) method, you can unmarshal the JSON bytes to map[string]interface{} and then validate the types you need with the JSON field keys.
An example of the JSON Unmarshaller looks like below.
type Page struct {
Title string `form:"title" binding:"required"`
Active bool
}
func (p *Page) UnmarshalJSON(bytes []byte) error {
var data map[string]interface{}
err := json.Unmarshal(bytes, &data)
if err != nil {
return err
}
actv, _ := data["active"]
if reflect.TypeOf(actv).Kind() != reflect.Bool {
return errors.New("active field should be a boolean")
}
p.Active = actv.(bool)
return nil
}
See the full example here in Playground.
After some more research, I've implemented Go-map-schema.
var page Page
src := make(map[string]interface{})
json.Unmarshal(jsonData, &src)
results, _ := schema.CompareMapToStruct(page, src, nil)
fmt.Println(results.MissingFields)
fmt.Println(results.MismatchedFields)
This works simple with the standard notations for an struct:
type Page struct {
Title string `json:"title" validator:"required"`
Active bool `json:"metaRobotsFollow" validate:"required,bool"`
}
You should use validator v10 available with Go
validator documentation go
For your use case you can use boolean validator
https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Boolean
type Page struct {
Title string `json:"title" binding:"required"`
Active bool `json:"active" binding:"required,boolean"`
}
Below is sample Test case with one positive and negative
func TestPage(t *testing.T) {
tests := []struct {
testName string
input string
wantErr bool
}{
{
testName: "positive test",
input: `{
"title": "first book title",
"active": false
}`,
wantErr: false,
}, {
testName: "wrong boolean",
input: `{
"title": "second book title",
"active": falsee
}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
var p Page
b := []byte(tt.input)
err := json.Unmarshal(b, &p)
assert.Nil(t, err, "got error %v", err)
})
}

gorm: populating related field of newly created data

I have the following related tables:
type Person struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Name string `json:"name"`
Surname string `json:"surname"`
}
type Book struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Title string `json:"title" binding:"required,min=2,max=100" gorm:"type:varchar(100)"`
Author Person `json:"author" binding:"required" gorm:"foreignkey:AuthorID"` // * here
AuthorID uint64 `json:"-"` // * here
WrittenIn string `json:"written_in" gorm:"type:varchar(5)"`
CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:CURRENT_TIMESTAMP"`
}
I can successfully create data with Create() method of gorm using this function:
func CreateBook(ctx *gin.Context) {
// validating input
var inputData CreateBookInput
if err := ctx.ShouldBindJSON(&inputData); err != nil {
ctx.JSON(401, gin.H{"status": "fail", "error": err.Error()})
}
// create book
book := models.Book{Title: inputData.Title, AuthorID: inputData.AuthorID, WrittenIn: inputData.WrittenIn}
database.DB.Create(&book).Preload("Author")
// database.DB.Preload("Author").Create(&book)
// database.DB.Set("gorm:auto_preload", true).Create(&book)
ctx.JSON(201, gin.H{"status": "success", "book": book})
}
I want to return the newly created book with its author. Expected response:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 3,
"name": "John",
"surname": "Smith"
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
But I couldn't find a way to populate related 'author'. So what I get is:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 0, // empty
"name": "", // empty
"surname": "" // empty
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
Itried these methods with no success:
database.DB.Create(&book).Preload("Author")
database.DB.Preload("Author").Create(&book)
database.DB.Set("gorm:auto_preload", true).Create(&book)
database.DB.Create(&book).Set("gorm:auto_preload", true)
How can I populate related field of newly created data?
One possible solution that you could try is to use the AfterCreate hook.
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
return tx.Model(b).Preload("Author").Error
}
You can find more info about hooks here.
Preload is chain method, Create is finisher method. only finisher method will generate and execute SQL.
So...
1 Find author by id after create book
if err ;= database.DB.Create(&book).Error; err != nil {
return err
}
// will not throw not found error
database.DB.Limit(1).Find(&book.Author, book.AuthorID)
ctx.JSON(201, gin.H{"status": "success", "book": book})
2 Load author data every times, use hook
// create and update
// func (b *Book) AfterSave(tx *gorm.DB) (err error) {
// just create
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
// handle error if you want
tx.Limit(1).Find(&b.Author, b.AuthorID)
return
}

How can I remove a property from a object

I have a User struct:
type User struct {
gorm.Model
Email string
Password string
AccountType int
CompanyId int
FirstName string
LastName string
PhoneNumber string
RecoveryEmail string
Contractor bool `gorm:"sql:'not null' default:'false'"`
}
I'm using this struct to get a row from the database using gorm:
// Get a specific user from the database.
func getUser(id uint) (*User, error) {
var user User
if err := database.Connection.Select("id, created_at, email, account_type, company_id, first_name, last_name").Where("id = ? ", id).First(&user).Error; err != nil {
return nil, err
}
fmt.Println(&user)
return &user, nil
}
My Gin hanlder:
// #Summary Attempts to get a existing user by id
// #tags users
// #Router /api/users/getUserById [get]
func HandleGetUserById(c *gin.Context) {
// Were using delete params as it shares the same interface.
var json deleteParams
if err := c.Bind(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "No user ID found, please try again."})
return
}
outcome, err := getUser(json.Id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Something went wrong while trying to process that, please try again.", "error": err.Error()})
log.Println(err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Successfully found user",
"user": outcome,
})
}
It returns back everything fine, but when I return &user the fields not selected are returned back with default values:
{
"message": "Successfully found user",
"user": {
"ID": 53,
"CreatedAt": "2018-06-24T00:05:49.761736+01:00",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Email": "jack#jackner.com",
"Password": "",
"AccountType": 0,
"CompanyId": 2,
"FirstName": "",
"LastName": "",
"PhoneNumber": "",
"RecoveryEmail": "",
"Contractor": false
}
}
Is there a way in go to remove empty or null properties from an object? Or will I have to send back an object instead with the values mapped to said new object? If there's a simple way of doing the former with a helper function I'd like to know how.
You can specify the omitempty tag in your object's fields definitions.
Example:
Email string `json:",omitempty"`
If you define the fields that way, empty values will not be present in the JSON output:
https://golang.org/pkg/encoding/json/#Marshal
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

Golang how to fetch specific key (from multistructure) from the rest api json response

Required json specific key: status -> name and id values only
getUrl := "https://www.test.com"
reqTransitionID, err := http.NewRequest("GET", getUrl, nil)
respTransitionID, err := client.Do(reqTransitionID)
if err != nil {
log.Fatal(err)
}
defer respTransitionID.Body.Close()
type Statuskey struct {
Id string `json:"id"`
Name string `json:"name"`
}
type TransitionID struct {
Status Statuskey `json:"status"`
}
jsonData, errrr := ioutil.ReadAll(respTransitionID.Body)
if errrr != nil {
fmt.Println("Error reading JSON data:", errrr)
return
}
fmt.Println(string(jsonData))
var ss TransitionID
json.Unmarshal(jsonData, &ss)
fmt.Println(ss.Status)
fmt.Println(ss.Status.Id)
fmt.Println(ss.Status.Name)
My Json response is :
{
"expand":"demo",
"id":"825",
"self":"TEST",
"key":"TEST",
"fields":{
"status":{
"self":"tst",
"description":"test",
"iconUrl":"test",
"name":"Closed",
"id":"6",
"statusCategory":{
"self":"test",
"id":3,
"key":"done",
"colorName":"green",
"name":"Done"
}
}
}
}
jsonDataSrc, errrr := ioutil.ReadAll(respTransitionID.Body)
if errrr != nil {
fmt.Println("Error reading JSON data:", errrr)
return
}
type Status struct {
Name string `json:"name"`
Id string `json:"id"`
}
type Fields struct {
Status Status `json:"status"`
}
type JsonResponse struct {
Fields Fields `json:"fields"`
}
res := JsonResponse{}
json.Unmarshal([]byte(jsonDataSrc), &res)
fmt.Println(res.Fields.Status.Id)
fmt.Println(res.Fields.Status.Name)
output - 6
Closed

undefined (cannot refer to unexported field or method)

I'm trying to refer Users struct from the models package and trying to access the model from control.But I take following errors.
controllers/user.go:87: user.create_date undefined (cannot refer to unexported field or method create_date)
controllers/user.go:88: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:104: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:119: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:136: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:151: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:166: user.user_id undefined (cannot refer to unexported field or method user_id)
Models.go
package models
import(
"time"
)
type Users struct {
user_id int `json:"user_id" form:"user_id" gorm:"column:user_id"`
user_login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
user_email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
user_password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
user_password_salt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
user_2factor_secret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
user_fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
user_description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
user_enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
user_verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
create_date time.Time `json:"create_date" gorm:"column:create_date"`
update_date time.Time `json:"update_date" gorm:"column:update_date"`
user_permission_cache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
user_role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
in controllers
package controllers
import (
"time"
"github.com/op/go-logging"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
"../models"
)
var loguser = logging.MustGetLogger("AdsAPI")
type AdsControllerUser struct {
DB gorm.DB
}
func (ac *AdsControllerUser) SetDB(d gorm.DB) {
ac.DB = d
ac.DB.LogMode(true)
}
func (ac *AdsControllerUser) CreateUsers(c *gin.Context) {
var user models.Users
// This will infer what binder to use depending on the content-type header.
c.Bind(&user)
// Update Timestamps
user.create_date = time.Now()
user.update_date = time.Now()
err := ac.DB.Save(&user)
if err != nil {
loguser.Debugf("Error while creating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to create user",
}
c.JSON(404, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) UpdateUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
//err := ac.DB.Model(&models.auth_User).Where("user_id = ?", id).Updates(&cm)
err := ac.DB.Where("user_id = ?", id).Updates(&user)
if err != nil {
loguser.Debugf("Error while updating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to update user",
}
c.JSON(403, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) DeleteUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
err := ac.DB.Where("user_id = ?", id).Delete(&user)
if err != nil {
loguser.Debugf("Error while deleting a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to delete user",
}
c.JSON(403, res)
return
}
content := gin.H {
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
Use Capitals for exported fields in struct, when referring struct in another package.
package models
import (
"time"
)
type Users struct {
ID int `json:"user_id" form:"user_id" gorm:"column:user_id"`
Login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
Email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
Password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
PasswordSalt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
TwoFactorSecret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
Fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
Description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
Enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
Verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
CreateDate time.Time `json:"create_date" gorm:"column:create_date"`
UpdateDate time.Time `json:"update_date" gorm:"column:update_date"`
PermissionCache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
Role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
Now do Users.ID to get fields.

Resources