Partial updates of objects - go

I want to enable update functionality for my User object in my fiber/gorm backend. It works fine when I update all fields together using the Save function. However, when I do not have all fields present in the update request (for example only the Birthday field but not the Phone field) it overwrites the rest of the fields with their respective null values.
func UserUpdateByID(c *fiber.Ctx) error {
db := database.DBConn
// Parse the body to fit user entity
user := entities.User{}
if err := c.BodyParser(&user); err != nil {
return c.Status(500).SendString(err.Error())
}
// Update record
record := db.Save(&user)
if record.Error != nil {
return c.Status(500).SendString(record.Error.Error())
}
return c.JSON(record.Value)
When I change the line with record := db.Save(&user) to
mappedData, _ := StructToMap(user)
record := db.Model(&entities.User{}).Update(mappedData)
I receive the error that Update can not handle map of interfaces: sql: converting argument $10 type: unsupported type map[string]interface {}, a map
Update 1:
The mentioned StructToMap function looks like this:
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
data, err := json.Marshal(obj)
if err != nil {
return
}
err = json.Unmarshal(data, &newMap) // Convert to a map
return
}
Update 2:
The User object looks like:
type User struct {
gorm.Model
Identity string
Birthday time.Time
Phone string
City string
...
ActivityData []Activity
}

Looking, on gorm doc(https://gorm.io/docs/update.html), you can do something like this :
Use the Updates instead of Update.
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
You can also use a db.Debug, to show the final query that gorm made, and see if matches with what are you expecting.

Related

Add root element to existing Json in Go lang

I'm trying to add string "Employee" to my existing JSON response. Also, we need to be able to generate this version of json based on an user condition. Only if the user condition is met, I need to generate second version of json with string "Employee" added. If not the first version without string "Employee" should be generated. How can I achieve it with out updating the existing struct and how can I check this with if clause to check for the condition and then generate json based on it?
Below is my existing json response in go
[
{
"EmpId":{
"String":"ABCD",
"Valid":true
},
"Department":{
"Float64":0,
"Valid":true
}
}
]
How can I get my json response like below with out changing existing struct based on input parameter?
{
"Employee":[
{
"EmpId":{
"String":"ABCD",
"Valid":true
},
"Department":{
"Float64":0,
"Valid":true
}
}
]
}
Below is my code:
Step 1: model folder
type EmployeeWithRoot struct {
Employee []Employee
}
type Employee struct {
EmpNbr sql.NullString `json:"EmpNbr"`
DateofJoin sql.NullString `json:"DateofJoin"`
DeptId sql.NullString `json:"DeptId"`
DeptName sql.NullString `json:"DeptName"`
}
Step 2: code folder
func GetEmpDetails(logRequestId string, logNestedLevel int, EmpNbr string, DateofJoin string) ([]model.EmployeeWithRoot, error) {
logFunctionFunctionName := "code.GetEmpDetails"
logStartTime := time.Now()
logNestedLevel++
defer configurations.TimeTrack(logFunctionFunctionName, logRequestId, logStartTime, logNestedLevel)
rows, err := db.Query(utils.SELECT_OF_EMP_AGGR, EmpNbr, DateofJoin, DeptId, DeptName)
if err != nil {
return nil, err
}
defer rows.Close()
var e []model.EmployeeWithRoot
for rows.Next() {
var x model.EmployeeWithRoot
err := rows.Scan(&x.Employee.EmpNbr, &x.Employee.DateofJoin, &x.Employee.DeptId,&x.Employee.DeptName)
if err != nil {
return nil, err
}
e = append(e, x)
}
err = rows.Err()
if err != nil {
return nil, err
}
return e, nil
}
STEP 3: API folder
Employee, err := code.GetEmpDetails(logRequestId, logNestedLevel, EmpNbr, DateofJoin)
if err != nil {
log.Panic(err)
}
marshalDataForRequestContentType(logRequestId, logNestedLevel, w, r, Employee)
I'm getting the below error.
x.Employee.EmpNbr undefined (type []model.Employee has no field or method EmpNbr)
x.Employee.DateofJoin undefined (type []model.Employee has no field or method DateofJoin)enter code here
x.Employee.DeptId undefined (type []model.Employee has no field or method DeptId)
x.Employee.DeptName undefined (type []model.Employee has no field or method DeptName)
Considering you're just wrapping it it an outer object, I don't see any reason you'd need to change the existing struct, just wrap it in a new one. I'll have to make some guesses/assumptions here since you've only shown the JSON and not the Go code that produces it, but assuming your existing JSON is produced by marshaling something like var response []Employee, the desired JSON could be produced in your condition by marshaling instead:
json.Marshal(struct{Employee []Employee}{response})
Working example: https://go.dev/play/p/vwDvxnQ96G_2
Use string concatenation:
func addRoot(json string) string {
return `{ "Employee":` + json + `}`
}
Run an example on the GoLang playground.
Here's the code if you are working with []byte instead of string:
func addRoot(json []byte) []byte {
const prefix = `{ "Employee":`
const suffix = `}`
result := make([]byte, 0, len(prefix)+len(json)+len(suffix))
return append(append(append(result, prefix...), json...), suffix...)
}
Run this example on the GoLang playground.
If you have some JSON in a byte slice ([]byte) then you can just add the outer element directly - e.g. (playground):
existingJSON := []byte(`[
{
"EmpId":{
"String":"ABCD",
"Valid":true
},
"Department":{
"Float64":0,
"Valid":true
}
}
]`)
b := bytes.NewBufferString(`{"Employee":`)
b.Write(existingJSON)
b.WriteString(`}`)
fmt.Println(string(b.Bytes()))
If this is not what you are looking for please add further details to your question (ideally your attempt as a minimal, reproducible, example)

Update method does not update zero value

Original Question
When using the Update method in GORM the new data does not get saved. i.e. I want to set a bool from true to false, but it stays true even after the Update method.
In the description of the method there is a warning: "WARNING when update with struct, GORM will not update fields that with zero value"
Since I am using a struct to update and false is the zero value of bool, this seems expected behaviour, but I don't see any reason why to do so and how to overcome this.
func UpdateData(c *fiber.Ctx) error {
db := database.DBConn
data := new([]entities.Data)
if err := c.BodyParser(&data); err != nil {
return err
}
db.Update(&data)
return c.JSON(data)
}
Solution Summary
First, as suggested I left out the new keyword when instantiating the structs. Then, I used a helper function (from here) for converting a struct to map while keeping the json alias as keys:
// StructToMap Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
data, err := json.Marshal(obj)
if err != nil {
return
}
err = json.Unmarshal(data, &newMap) // Convert to a map
return
}
Then I loop over each element in the data slice in order to convert it and update it one by one:
func UpdateData(c *fiber.Ctx) error {
db := database.DBConn
data := []entities.Dard{}
if err := c.BodyParser(&data); err != nil {
return err
}
for _, record := range data {
mappedData, _ := StructToMap(record)
db.Model(&entities.Data{}).Update(mappedData)
}
return c.JSON(data)
}
*Error handling is obviously reduced in this example.
From official doc
NOTE When update with struct, GORM will only update non-zero
fields, you might want to use map to update attributes or use Select
to specify fields to update
So use map[string]interface{} to update non-zero fields also. Example:
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
As you have struct already you can convert struct into map[string]interface{} (See details about conversion) then update. Another way is change the type of field as pointer.

Idiomatic way to update with GORM and echo-framework

Hi new in golang and trying to make rest API
I want to updating entity passing only needed datas with GORM and golang echo framework.
And I get this error :
"strconv.ParseUint: parsing \"ea78944c-a2a9-4813-86e7-10b199d0f002\": invalid syntax"
I use echo.Bind() func to bind formdata (I use postman) with my Club struct.
PS : I use xid to external id and keep my int id to interne work.
Expected :
I'm gonna find Club in my database by id (get with url param) with GORM and set it up in a new club struct.
After that, I bind my club struct with my formdata.
And finally save it with Save() GORM func. => get error
Reality :
I'm gonna find Club in my database by id (write hardcoding string id) with GORM and set it up in a new club struct.
After that, I bind my club struct with my formdata.
And finally save it with Save() GORM func. => works
Conclusion :
I don't know how to pass url parameter AND formdata in PUT method using echo.Bind()
Here my Club struct :
// Club struct
type Club struct {
Base
Name string `json:"name;" gorm:"type:varchar(255);not null" validate:"required"`
Slug string `json:"slug;" gorm:"type:varchar(255);unique;not null"`
Website string `json:"website;" gorm:"type:varchar(255)"`
Lat float32 `json:"lat;" gorm:"lat;" sql:"type:decimal(8,6);" validate:"required,numeric"`
Lng float32 `json:"lng;" gorm:"lng;" sql:"type:decimal(9,6);" validate:"required,numeric"`
Logo string `json:"logo;" gorm:"type:varchar(100)" validate:"required"`
Phone string `json:"phone;" gorm:"type:varchar(20)" validate:"required"`
}
My FindById func :
// GetClubByXID get club by xid
func GetClubByXID(c echo.Context, xid string) (*schemas.Club, error) {
club := new(schemas.Club)
if result := db.Where("xid = ?", xid).First(&club); result.Error != nil {
return nil, result.Error
}
return club, nil
}
And here my updating func :
func UpdateClub(c echo.Context) error {
xid := c.Param("id") // => doesn't work
// xid := "ea78944c-a2a9-4813-86e7-10b199d0f002" // => work
club, err := models.GetClubByXID(c, xid)
if err != nil {
return c.JSON(http.StatusNotFound, err)
}
if err := c.Bind(club); err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
db.Save(&club)
return c.JSON(http.StatusOK, echo.Map{
"club": &club,
})
}
My updating route :
API.PUT("/updateClub/:id", handlers.UpdateClub) // => doesn't work
// API.PUT("/updateClub", handlers.UpdateClub) // => work
When I write with hardcoding my xid ea78944c-a2a9-4813-86e7-10b199d0f002 in my updating func it's work like a charm but I can't combine my url and my formdata with echo.Bind()
my err if I try http://localhost:1323/api/updateClub/ea78944c-a2a9-4813-86e7-10b199d0f002:
"strconv.ParseUint: parsing \"ea78944c-a2a9-4813-86e7-10b199d0f002\": invalid syntax"
Thanks for reading hope someone can help me :)
Can you try to c.Param("xid") instead of c.Param("id") and change the routing parameter as API.PUT("/updateClub/:xid", handlers.UpdateClub)

Golang Rest api data not showing in postman

I am facing an issue where i have made an api in Go every thing work fine but i am not getting data in postman. When i print the data in logs i am getting the data properly but it is showing blank data in postman.
authorizeModel.go
func GetSkillList() map[string]interface{} {
db := GetDB()
var (
// id int
skillName string
)
type SkillList struct {
name string
}
skillList := SkillList{}
skillArr := []SkillList{}
rows, err := db.Query("select DISTINCT(name) as name from skills where company_id IN ('2') and name != 'Skill Needed' order by name")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&skillName)
if err != nil {
log.Fatal(err)
}
skillList.name = skillName
skillArr = append(skillArr, skillList)
}
response := u.Message(true, "Skill list retrieved successfully")
response["data"] = skillArr
log.Println(skillArr)
response["authorization"] = false
return response
}
authController.go
var SkillTagList = func(w http.ResponseWriter, r *http.Request) {
resp := models.GetSkillList()
u.Respond(w, resp)
}
routes.go
router.HandleFunc("/api/v1/authorize/skillTagList", controllers.SkillTagList).Methods("POST")
If you see authorizeModel.go i have printed my data in logs i am getting that data successfully in logs. But see the postman screenshot below.
You have to rename name to Name
I'm not sure what is u.Respond(), so I will assume it's a helper function of some framework that you are using, and I will assume u.Respond() is internally using json.Marshal.
If your struct has unexported fields(fields name starting with lowercase letter, in your case name), json.Marshal cannot access those field, and the result won't have name field. That is why you are getting empty objects in JSON.

How to create a DateTime field in bigquery and update it in golang?

here is basically my creation script for bigquery in golang :
type data_pix struct {
Id string
IdC string
Stamp int64
Tag []string
}
func createTable(client *bigquery.Client, datasetID, tableID string) error {
ctx := context.Background()
// [START bigquery_create_table]
schema, err := bigquery.InferSchema(data_pix{})
if err != nil {
return err
}
table := client.Dataset(datasetID).Table(tableID)
if err := table.Create(ctx, schema); err != nil {
return err
}
// [END bigquery_create_table]
return nil
}
for the moment i use mainly a timestamp in Int64.
But i am looking for any example on how to add Datetime to my struct and btw add Datetime to my data
Thanks and regards
I have not used the bigquery, however I had a look at the godoc and source code.
It seems, you have to use data type civil.DateTime reference in the struct.
For e.g:
As per godoc and source code, following should create DateTime field.
type data_pix struct {
Id string
IdC string
Stamp civil.DateTime
Tag []string
}
schema, err := bigquery.InferSchema(data_pix{})
// now schema should represent DateTime Field
There is a function to get civil.DateTime from time.Time. I would suggest you have a look at this go sourcecode to know more.

Resources