gorm: populating related field of newly created data - go

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
}

Related

How to create custom unmarshal for 2 types map[string]interface{} and []interface{}

Here's my sample go playground https://go.dev/play/p/MosQs62YPvI
My curl API return 2 kind of return, can any of the ff:
{
"code": 200,
"message": "Success",
"data": {
"list": {
"1": {
"user": "user A",
"status": "normal"
},
"2": {
"user": "user A",
"status": "normal"
}
},
"page": 1,
"total_pages": 2000
}
}
or
{
"code": 200,
"message": "Success",
"data": {
"list": [
{
"user": "user A",
"status": "normal"
},
{
"user": "user B",
"status": "normal"
}
],
"page": 1,
"total_pages": 5000
}
}
How to unmarshal it properly?
Here's my struct
type User struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
List []struct {
User string `json:"user"`
Status string `json:"status"`
} `json:"list"`
Page int `json:"page"`
TotalPages int `json:"total_pages"`
} `json:"data"`
}
Here's how I unmarshal it
err = json.Unmarshal([]byte(io_response), &returnData)
if err != nil {
log.Println(err)
}
I have tried creating my own unmarshal but I have issues converting it to map[string]interface{}
Can you please help me? Or is there any better way?
type UserItem struct {
User string `json:"user"`
Status string `json:"status"`
}
type UserList []UserItem
func (ul *UserList) UnmarshalJSON(data []byte) error {
switch {
case len(data) == 0 || string(data) == `null`:
return nil
case data[0] == '[': // assume it's a JSON array
return json.Unmarshal(data, (*[]UserItem)(ul))
case data[0] == '{': // assume it's a JSON object
obj := make(map[string]UserItem)
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
for _, v := range obj {
*ul = append(*ul, v)
}
return nil
default:
return fmt.Errorf("unsupported json type")
}
return nil
}
https://go.dev/play/p/Y5PAjrmPhy2

GORM The child relation has returned empty. How Can I fix it?

I'm trying to get child relation, but it doesn't work.
I have two models, as you can see below. How can I get "FirstCompetitor" relation in result. Can I use auto:preload in that case, or How can preload it? (I tried some preload query before where query, However It didn't work.). The child relation has returned empty.
package model
import (
"errors"
"time"
"gorm.io/gorm"
)
// FixtureMatches the match model
type FixtureMatches struct {
ID uint `gorm:"primary_key" json:"id"`
FirstCompetitor FixtureCompetitors `gorm:"foreignKey:FirstCompetitor;references:ID" json:"home"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// TableName for gorm
func (FixtureMatches) TableName() string {
return "fixture_matches"
}
// GetFirstByID gets the user by his ID
func (u *FixtureMatches) GetFirstByID(id string) error {
err := DB().Where("id=?", id).First(u).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrDataNotFound
}
return err
}
Response:
{
"id": 1,
"home": {
"id": 0,
"name": ""
},
"createdAt": "2022-06-06T05:28:53Z",
"updatedAt": "2022-06-06T05:33:01Z"
}

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

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.

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.

Parsing JSONSchema to struct type in golang

So, my use case consists of parsing varying JSON schemas into new struct types, which will be further used with an ORM to fetch data from a SQL database. Being compiled in nature, I believe there will not be an out-of-the-box solution in go, but is there any hack available to do this, without creating a separate go process. I tried with reflection, but could not find a satisfactory approach.
Currently, I am using a-h generate library which does generate the structs, but I am stuck at how to load these new struct types in go runtime.
EDIT
Example JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Address",
"id": "Address",
"type": "object",
"description": "address",
"properties": {
"houseName": {
"type": "string",
"description": "House Name",
"maxLength": 30
},
"houseNumber": {
"type": "string",
"description": "House Number",
"maxLength": 4
},
"flatNumber": {
"type": "string",
"description": "Flat",
"maxLength": 15
},
"street": {
"type": "string",
"description": "Address 1",
"maxLength": 40
},
"district": {
"type": "string",
"description": "Address 2",
"maxLength": 30
},
"town": {
"type": "string",
"description": "City",
"maxLength": 20
},
"county": {
"type": "string",
"description": "County",
"maxLength": 20
},
"postcode": {
"type": "string",
"description": "Postcode",
"maxLength": 8
}
}
}
Now, in the above-mentioned library, there is a command line tool, which generates the text for struct type for above json as below:
// Code generated by schema-generate. DO NOT EDIT.
package main
// Address address
type Address struct {
County string `json:"county,omitempty"`
District string `json:"district,omitempty"`
FlatNumber string `json:"flatNumber,omitempty"`
HouseName string `json:"houseName,omitempty"`
HouseNumber string `json:"houseNumber,omitempty"`
Postcode string `json:"postcode,omitempty"`
Street string `json:"street,omitempty"`
Town string `json:"town,omitempty"`
}
Now, the issue is that how to use this struct type without re-compilation in the program. There is a hack, where I can start a new go process, but that doesn't seem a good way to do it. One other way is to write my own parser for unmarshalling JSON schema, something like:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var f interface{}
json.Unmarshal(b, &f)
m := f.(map[string]interface{})
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", vv)
case int:
fmt.Println(k, "is int", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
Can someone please suggest some pointers to look for. Thanks.
So it looks like you're trying to implement your own json marshalling. That's no biggie: the standard json package already supports that. Just have your type implement the MarshalJSON and UnmarshalJSON functions (cf first example on the docs). Assuming some fields will be shared (eg schema, id, type), you can create a unified type like this:
// poor naming, but we need this level of wrapping here
type Data struct {
Metadata
}
type Metadata struct {
Schema string `json:"$schema"`
Type string `json:"type"`
Description string `json:"description"`
Id string `json:"id"`
Properties json.RawMessage `json:"properties"`
Address *Address `json:"-"`
// other types go here, too
}
Now all properties will be unmarshalled into a json.RawMessage field (essentially this is a []byte field). What you can do in your custom unmarshall function now is something like this:
func (d *Data) UnmarshalJSON(b []byte) error {
meta := Metadata{}
// unmarshall common fields
if err := json.Unmarshal(b, &meta); err != nil {
return err
}
// Assuming the Type field contains the value that allows you to determine what data you're actually unmarshalling
switch meta.Type {
case "address":
meta.Address = &Address{} // initialise field
if err := json.Unmarshal([]byte(meta.Properties), meta.Address); err != nil {
return err
}
case "name":
meta.Name = &Name{}
if err := json.Unmarshal([]byte(meta.Properties), meta.Name); err != nil {
return err
}
default:
return errors.New("unknown message type")
}
// all done
d.Metadata = meta // assign to embedded
// optionally: clean up the Properties field, as it contains raw JSON, and is exported
d.Metadata.Properties = json.RawMessage{}
return nil
}
You can do pretty much the same thing for marshalling. First work out what type you're actually working with, then marshal that object into the properties field, and then marhsal the entire structure
func (d Data) MarshalJSON() ([]byte, error) {
var (
prop []byte
err error
)
switch {
case d.Metadata.Address != nil:
prop, err = json.Marshal(d.Address)
case d.Metadata.Name != nil:
prop, err = json.Marshal(d.Name) // will only work if field isn't masked, better to be explicit
default:
err = errors.New("No properties to marshal") // handle in whatever way is best
}
if err != nil {
return nil, err
}
d.Metadata.Properties = json.RawMessage(prop)
return json.Marshal(d.Metadata) // marshal the unified type here
}

Resources