GO: import a struct and rename it in json - go

I have built a database in go with gorm. For this I created a struct and with this struct I created a table. So far so good. In the backend everything works, but in the frontend the problem is that the JSON which is called always returns the ID in upper case and swagger generates me an ID which is lower case. Is there a way in Go that I can overwrite the imported struct from gorm with a JSON identifier?
import "gorm.io/gorm"
type Report struct {
gorm.Model
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
This Struct gives me the following response
{
"ID": 8,
"CreatedAt": "2022-11-15T20:45:16.83+01:00",
"UpdatedAt": "2022-12-27T21:34:17.871+01:00",
"DeletedAt": null
"createdBy": {
"ID": 1,
"CreatedAt": "2022-11-15T20:02:17.497+01:00",
"UpdatedAt": "2022-11-15T20:02:17.497+01:00",
...
},
"archived": true,
}
Is there a way to make the ID lowercase (like Archived)? Or can I adjust it at swaggo so that it is generated in upper case.
What I have seen is that you can make the table without this gorm.Model and define all the attributes yourself. The problem is that I then have to create all the functionalities (delete, update, index, primary key, ...) of these columns myself.

You can use the mapstructure package.
mapstructure is a Go library for decoding generic map values to structures and vice versa
If you want to embed ID field from gorm.Model with custom json tag, and you want it to be on the same struct and not in a "substruct", you can use mapstructure:",squash" tag on the embedded model.
type Model struct {
ID uint `json:"id"`
}
type Report struct {
Model `mapstructure:",squash"`
Archived bool `json:"archived"`
}
func main() {
input := map[string]interface{}{
"id": 1,
"archived": false,
}
var report Report
if err := mapstructure.Decode(input, &report); err != nil {
panic(err)
}
fmt.Println("Report ID:", report.ID)
fmt.Println("Report ID via Model:", report.Model.ID)
}
As you can observe, with mapstructure.Decode method you can convert map to struct with the squash option, and you can then access ID of report directly. Note that you can still access report.Model and all its fields.
With mapstructure, you can make the ID lowercase as you wanted, and also accessable from the report struct directly, not only from a Model substruct.

I create my own gorm-model-struct:
type GormModel struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
} //#name models.gormModel
I import this struct into the other structs:
type Report struct {
GormModel
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
Important is, that you add the json-key and set the attribute name.

Related

GORM - How to have embedded struct and foreign key relationship in database

I currently have a struct model where I declare a many-to-many relationship between two tables: StructA and StructB. StructB has a list of StructA, and this association has its own attribute.
// My current model
type StructA struct {
ID uint `json:"id" gorm:"primaryKey"`
PropA string `json:"propA"`
}
type StructB struct {
ID uint `json:"id" gorm:"primaryKey"`
ListStructA []JoinTable `json:"listStructA"`
}
type JoinTable struct {
StructAID uint `json:"-" gorm:"primaryKey"`
StructBID uint `json:"-" gorm:"primaryKey"`
StructA StructA `json:"structA"`
JoinProp int `json:"joinProp"`
}
And the marshalling of StructB produces this kind of result
// Current StructB json
{
"id": 1,
"listStructA": [
{
"joinProp": 1,
"structA": {
"id": 1,
"propA": "some prop"
}
}
]
}
With this implementation, my database model is efficient. But I would like to have all attributes in the listStructA objects at the same hierarchical level, because it is quite obvious for the consumer of this json that this part of the data contains structB attributes, and it would feel off-putting to type structB.listStructA[0].structA.propA instead of structB.listStructA[0].propA. So my end goal is to have this structure when marshalling structB.
// Wanted StructB json
{
"id": 1,
"listStructA": [
{
"id": 1,
"propA": "some prop",
"joinProp": 1,
}
]
}
I also point out that in the application logic, it makes sense to type structB.listStructA[0].joinProp, but not store JoinProp in StructA
One obvious way to do it, is to make the StructA attribute from the JoinTable as an embedded struct
type JoinTable struct {
StructA
StructBID uint `json:"-" gorm:"primaryKey"`
JoinProp int `json:"joinProp"`
}
But this will duplicate the data of StructA in JoinTable's database table, instead of referencing it (ruining the database model). The only thing about embedded struct I was able to find in the official documentation is in this section, but it explains how to do the exact opposite (having embedded struct in database but not in code).
So is there something prebuilt in GORM to have best of both world? Or does this require an implementation I overlooked?
I can always implement the MarshalJSON method to JoinTable in last resort, but I would prefer not to do it if there is a smarter way of thing.

GORM updating null field when calling Updates()?

According to GORM's docs:
Updates supports update with struct or map[string]interface{}, when
updating with struct it will only update non-zero fields by default
I have an entry in my database already for the Service with ID, abc123. I am attempting to take an object that looks like the one below:
Service{
ID: "abc123",
Name: "new service name",
CreatedAt: nil,
}
And use it to update the my existing record. But when I call:
tx.Model(&service).Updates(service)
the CreatedAt value in the database is overwritten with nil. How can I update my database record without overwritting the CreatedAt value?
Update: Below is my Service struct
type Service struct {
ID string `gorm:"not null;type:char(32);primary_key;column:id"`
Name string `json:"name" gorm:"size:50;not null;"`
CreatedAt *time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}
I've tried two different variations for my Service struct. the other is with CreatedAt being of type time.Time instead of *time.Time. With *time.Time it will overwrite the value in my DB with a null value. With time.Time it attempts to overwrite the value in the DB with an uninitialized time value and throws the error: Error 1292: Incorrect datetime value: '0000-00-00' for column 'created_at' at row 1
By default, gorm will not update default(zero) or nil values.
If you want to have control over it use something as I described below.
You can use nullable types provided by sql package or create your own.
type Model struct {
Amount *sql.NullFloat64
}
// amount will not be updated
gorm.Updates(Model{Amount: nil})
// amount will be updated as a null
gorm.Updates(Model{Amount: &sql.NullFloat64{}})
// amount will be updated as a 10.50
gorm.Updates(Model{Amount: &sql.NullFloat64{Float64: 10.50, Valid: true}})
A zero value, or a default value, for a time.Time field type inside a struct is time.Time{}. When using Updates, either don't populate the CreatedAt field, or assign time.Time{} value to it.
In the example below, the default or zero value is printed out for CreatedAt field in both cases.
package main
import (
"fmt"
"time"
)
type T struct {
CreatedAt time.Time
C int
S string
}
func main() {
fmt.Println(T{C: 1, S: "one"})
fmt.Println(T{C: 2, S: "two", CreatedAt: time.Time{}})
}
// {0001-01-01 00:00:00 +0000 UTC 1 one}
// {0001-01-01 00:00:00 +0000 UTC 2 two}
EDIT:
Also, I'm not sure how even CreatedAt: nil, compiles if the CreatedAt field is of time.Time type, and not *time.Time.
Since you've updated the Service struct and CreatedAt field type to *time.Time, following should work:
tx.Model(&service).Updates(Service{Name: service.Name}) // add all fields that you want to be updated.
// resulting query
// UPDATE services SET name = 'new service name' WHERE id = 'abc123';
An official GORM example is here
Additionally, you can omit the created_at field like this:
tx.Model(&service).Omit("created_at").Updates(service)
use map
https://gorm.io/docs/update.html#Updates-multiple-columns
tx.Model(&service).Updates(map[string]interface{}{"ID": "abc123", "Name": "new service name", "CreatedAt": nil})

Better way of decoding json values

Assume a JSON object with the general format
"accounts": [
{
"id": "<ACCOUNT>",
"tags": []
}
]
}
I can create a struct with corresponding json tags to decode it like so
type AccountProperties struct {
ID AccountID `json:"id"`
MT4AccountID int `json:"mt4AccountID,omitempty"`
Tags []string `json:"tags"`
}
type Accounts struct {
Accounts []AccountProperties `json:"accounts"`
}
But the last struct with just one element seems incorrect to me. Is there a way I could simply say type Accounts []AccountProperties `json:"accounts"` instead of creating an entire new struct just to decode this object?
You need somewhere to store the json string accounts. Using a:
var m map[string][]AccountProperties
suffices, though of course you then need to know to use the string literal accounts to access the (single) map entry thus created:
type AccountProperties struct {
ID string `json:"id"`
MT4AccountID int `json:"mt4AccountID,omitempty"`
Tags []string `json:"tags"`
}
func main() {
var m map[string][]AccountProperties
err := json.Unmarshal([]byte(data), &m)
fmt.Println(err, m["accounts"])
}
See complete Go Playground example (I had to change the type of ID to string and fix the missing { in the json).
As Dave C points out in comments, this is no shorter than just using an anonymous struct type:
var a struct{ Accounts []AccountProperties }
in terms of the Unmarshall call (and when done this way it's more convenient to use). Should you want to use an anonymous struct like this in a json.Marshall call, you'll need to tag its single element to get a lowercase encoding: without a tag it will be called "Accounts" rather than "accounts".
(I don't claim the map method to be better, just an alternative.)

Referencing GORM auto-generated fields in Go

I'm writing my first API, so bear with me. I am using Go, Postgres and GORM and a slew of other things I'm still picking up but I ran into an issue with GORM's AutoMigrate.
Initially my User struct looked like this:
type User struct {
gorm.Model
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
And when I ran db.AutoMigrate(&User{}) It auto-generated an id field in my User table (along with several date fields), which I wanted. What I am hung up on is figuring out how to reference these fields in my app. I have modified my User struct to now look like this:
type User struct {
gorm.Model
ID int `gorm:"primary_key" json:"id"`
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
But instead of linking the two id fields, when I access the stored user object as shown:
user := model.User{}
if err := db.First(&user, model.User{Email: email}).Error; err != nil {
respondError(w, http.StatusNotFound, err.Error())
return nil
}
there are now two distinct fields, the auto-generated and my own:
{
"ID": 2,
"CreatedAt": "2018-04-28T21:14:20.828547-04:00",
"UpdatedAt": "2018-04-28T21:14:20.828547-04:00",
"DeletedAt": null,
"id": 0,
"email": "joeynelson#gmail.com",
"password": <hash>
}
I realize the answer is likely right in front of my face, there must be a way to reference these auto-generated fields, right?
gorm.Model is a struct including some basic fields, which including fields ID, CreatedAt, UpdatedAt, DeletedAt.
Reference: http://gorm.io/docs/conventions.html#gorm-Model

Golang imported fields do not act the same as standard field declarations

I'm going to try to simplify the problem down rather than bring the whole project into scope so if you have questions I'll try to update with more information.
I have 3 structs I'm working with:
type Ticket struct{
ID bson.ObjectID `json:"id" bson:"_id"`
InteractionIDs []bson.ObjectId `json:"interactionIds" bson:"interactionIds"`
TicketNumber int `json:"ticketNumber" bson:"ticketNumber"`
Active bool `json:"active" bson:"active"`
// Other fields not included
}
type TicketInteraction struct{
ID bson.ObjectID `json:"id" bson:"_id"`
Active bool `json:"active" bson:"active"`
// Other fields not included
}
type TicketLookupTicket struct{
Ticket
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
Then I have an mgo pipe that I'm working with to 'join' the two collections together
var tickets []TicketLookupTicket
c := mongodb.NewCollectionSession("tickets")
defer c.Close()
pipe := c.Session.Pipe(
[]bson.M{
"$lookup": bson.M{
"from": "ticketInteractions",
"localField": "interactionIds",
"foreignField": "_id",
"as": "interactions",
}
},
)
pipe.All(&tickets)
Now tickets should be populated with the result from the database, but what is actually happening is only the interactions within each ticket has been populated. It ends up looking something like this:
[
{
ID: ObjectIdHex(""),
InteractionIDs: nil,
TicketNumber: 0,
Active: false,
// Other Fields, all with default values
Interactions: []{
{
ID: ObjectIdHex("5a441ffea1c9800148669cc7"),
Active: true,
// Other Fields, with appropriate values
}
}
}
]
Now if I manually declare some of the Ticket structs fields inside the TicketLookupTicket struct, those fields will start populating. Ex:
type TicketLookupTicket struct{
Ticket
ID bson.ObjectId `json:"id" bson:"_id"`
TicketNumber int `json:"ticketNumber" bson:"ticketNumber"`
Active bool `json:"active" bson:"active"`
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
Now ID, TicketNumber, and Active will start populating, but the remaining fields won't.
Can anyone explain why the imported Ticket fields aren't behaving the same as the declared fields?
Per the documentation, you need to add the inline tag to the field:
type TicketLookupTicket struct{
Ticket `bson:",inline"`
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
inline Inline the field, which must be a struct or a map.
Inlined structs are handled as if its fields were part
of the outer struct. An inlined map causes keys that do
not match any other struct field to be inserted in the
map rather than being discarded as usual.
By default, it assumes that the embedded field Ticket should be filled by an object at TicketLookupTicket.Ticket.

Resources