Search code examples
gogo-gin

Ignoring createdAt field when create/update, but return to JSON response


I have a struct in Go

type ExternalSystem struct {
    ID        uint            `gorm:"primary_key"`
    Name      string          `json:"name" binding:"required"`
    CreatedAt *time.Time      `json:"createdAt" binding:"-"`
    DeletedAt *gorm.DeletedAt `json:"deletedAt" binding:"-"`
}

And have controller (route)

func CreateExternalSystemAction(c *gin.Context) {
    appG := app.Gin{C: c}

    externalSystem := models.ExternalSystem{}

    if err := c.ShouldBindJSON(&externalSystem); err != nil {
        appG.C.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    db := models2.DB
    db.Create(&externalSystem)

    appG.C.JSON(http.StatusCreated, externalSystem)
}

func UpdateExternalSystemAction(c *gin.Context) {
    appG := app.Gin{C: c}
    db := models2.DB

    var externalSystem models.ExternalSystem

    db.Where("id = @id", sql.Named("id", c.Param("id"))).First(&externalSystem)

    if err := c.ShouldBindJSON(&externalSystem); err != nil {
        appG.C.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    db.Updates(&externalSystem)

    appG.C.JSON(http.StatusOK, externalSystem)
}

How can I ignore the completion of the field CreatedAt/UpdatedAt when creating or updating an object? So that the server, and not the client, is responsible for filling in these fields, what does JSON send to me? However, I need the value of this field to go to the client in the response.

Currently, when I send JSON:

{
    "name": "System 3",
    "createdAt": "2023-12-25T22:04:10.012034+04:00"
}

Gin binding this value to struct. To summarize again, I want to ignore these fields for writing (when received from the client), but give them in the response for reading.

I tried:

binding:"-", but this doesnt work (biding for validation)

json:"-", but this hide field for response


Solution

  • I thought about the solution for a long time. And after reading the comments under my question. I came to an option that completely suits me. However, the approach is still not similar to what I have used in other languages (PHP Symfony, Python Django)

    Model

    type System struct {
        ID        uint
        Name      string
        CreatedAt time.Time
        UpdatedAt time.Time
    }
    
    type InputSystem struct {
        Name string
    }
    
    func (inputSystem *InputSystem) Convert() System {
        system := System{
            Name: inputSystem.Name,
        }
        return system
    }
    
    func (system *System) Save() (*System, error) {
        err := DB.Create(system).Error
    
        if err != nil {
            return &System{}, err
        }
    
        return system, err
    }
    
    func (system *System) BeforeSave(*gorm.DB) error {
        system.Name = html.EscapeString(strings.TrimSpace(system.Name))
        return nil
    }
    
    func (system *System) BeforeUpdate(*gorm.DB) error {
        system.Name = html.EscapeString(strings.TrimSpace(system.Name))
        return nil
    }
    

    Controller (route)

    func CreateSystemAction(c *gin.Context) {
    
        var inputSystem models.InputSystem
    
        if err := c.BindJSON(&inputSystem); err != nil {
            c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
    
        dbSystem := inputSystem.Convert()
    
        _, err := dbSystem.Save()
        if err != nil {
            return
        }
    
        c.JSON(http.StatusCreated, dbSystem)
    }
    

    If there are better suggestions, I will be glad to receive comments