Gin中浮点数 NaN JSON序列化问题

作者:matrix 发布时间:2024-12-24 分类:Golang

无意把除数为0的计算放了进来,既然没有发现panic,页面却显示空白。看起来像是没有被全局recover捕获~

gin 控制台显示

Error #01: json: unsupported value: NaN

排查

断点调试下发现json处理的问题,error被push到gin的c.Error里面,gin被判定为私有类型错误,所以没有panic

~/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go

func (c *Context) Render(code int, r render.Render) {
    c.Status(code)

    if !bodyAllowedForStatus(code) {
        r.WriteContentType(c.Writer)
        c.Writer.WriteHeaderNow()
        return
    }

    if err := r.Render(c.Writer); err != nil {
        // Pushing error to c.Errors
        _ = c.Error(err)
        c.Abort()
    }
}

r.Render(c.Writer)内部会json序列化,error被返回到r.Render。这个错误信息就直接给了上下文的c.Error(err)

原因

Json序列化时存在math.Nan特殊浮点型数据会导response失败,
c.JSON没有任何返回也就是空白页面显示了。

测试

func main() {

    data := map[string]interface{}{
        "name":     "123",
        "name_nan": math.NaN(), // json: unsupported value: NaN
        // "name_inf": math.Inf(1), // json: unsupported value: +Inf
        // "name_inf": math.Inf(-1), // json: unsupported value: -Inf
    }

    a, err := json.Marshal(data)
    fmt.Println(a, err)

}

NaN这种其他特殊意义值 Inf 都回导致 JSON 序列化异常

解决办法

gin中添加错误处理中间件

func ErrorHandler() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Next() // 继续执行请求处理链

    //中间件处理完成后进行错误收集
    if len(c.Errors) > 0 {
      // 迭代错误并处理
      for _, err := range c.Errors {
        c.JSON(http.StatusInternalServerError, gin.H{
          "code":    http.StatusInternalServerError,
          "message": err.Error(),
          "data":    nil,
        })
        c.Abort()
        return
      }
    }
  }
}

其他办法就是自行实现JSON序列化操作或者判断NaNInf数据

参考:

https://blog.axiaoxin.com/post/2021-11-21-Golang-%E8%BF%90%E8%A1%8C%E6%97%B6%E9%99%A4%E6%95%B0%E4%B8%BA-0-%E8%BF%94%E5%9B%9E-inf/

https://blog.csdn.net/qq_36268452/article/details/124809417

https://blog.csdn.net/qq_40227117/article/details/122186916

GORM中使用虚拟字段

作者:matrix 发布时间:2023-11-30 分类:Golang

使用gorm时,可能需要处理虚拟字段(不在数据库中实际存在的字段)的情况。可以使用结构体tag标签来支持

User结构体模型

type User struct {
    ID    uint    `gorm:"primaryKey;not null"` // 主键ID

    // 虚拟字段
    Isvip int     `gorm:"-;default:0"`         // 是否vip 1是 0否
}

说明:

IsVip字段被标记为 gorm:"-" ,表示虚拟字段。gorm在进行数据库操作(如查询、插入、更新等)时,将不会考虑此字段。同时,可以使用default 标签为其指定默认值。

自定义获取器

自定义一个Get方法 例如,下面的GetIsVip方法会基于用户的VIP状态来返回相应的值:

func (u *User) GetIsVip() int {
    if u.Vip != nil && u.Vip.IsActive == 1 {
        return 1
    }
    return 0
}

应用获取器

在查询User对象时,GORM提供了 AfterFind 方法来自动执行特定逻辑。这在处理虚拟字段时很有用:

// 查询数据时自动赋值字段
func (u *User) AfterFind(tx *gorm.DB) (err error) {
    if u.Vip == nil {
        //TIPS:Association方法手动触发模型关联。如果使用Preload会再次查询User主表,不推荐
        // tx.Preload("Vip").First(&u, u.ID) //不推荐
        tx.Model(u).Association("Vip").Find(&u.Vip)
    }
    u.Isvip = u.GetIsVip() // 手动触发虚拟字段计算

    return
}

说明:

首先检查VIP信息是否已加载。如果未加载,则使用Association方法手动触发加载。之后,我们使用前面定义的GetIsVip方法来计算并设置Isvip字段的值。

注意

使用AfterFind可能会覆盖Isvip字段的默认值(如default:0

ShouldBindQuery获取GET参数默认值

作者:matrix 发布时间:2023-10-31 分类:Golang

图片5702-ShouldBindQuery读取GET参数默认值

验证器结构体的form标签中设置default即可,仅测试ShouldBindQuery有效读取,其他绑定方法未知

验证器结构体

type UserListValidator struct {
    Type     string `form:"type,default=RECOMMEND" binding:"omitempty,oneof=NEAR RECOMMEND" label:"列表类型"` //列表类型 NEAR:附近(默认) RECOMMEND:推荐
}

控制器方法

func (u *UserController) List(ctx *gin.Context) {
    validator := validators.UserListValidator{}
    if err := ctx.ShouldBindQuery(&validator); err != nil {
        u.JSONResponseError(ctx, err)
        return
    }
    //validator.Type 

参考:

https://github.com/gin-gonic/gin/issues/1052#issuecomment-1609678741

https://www.vksir.zone/posts/go_struct_default/