golang修改time类型字段的json格式
背景大概如下:
项目框架为gin, 数据库orm为gorm, 一个比较简单的model, 包含某个time类型字段(如: CreateTime), 返回数据给前端时, 返回的格式为2022-01-17T00:00:00+08:00
, 这种数据结构, 返回给前端时, 前端如果直接用来渲染, 这种格式的时间的可读性不高, 故需要转换成用户较好理解的格式, 如: 2022-01-17 00:00:00
解决
原始代码
package main
import (
"encoding/json"
"fmt"
"time"
)
type Person struct {
Id int `json:"id"`
BirthTime time.Time `json:"birth_time"`
}
func main() {
birthTime, _ := time.ParseInLocation("2006-01-02 15:04:05", "2022-01-17 00:00:00", time.Local)
out, _ := json.Marshal(
Person{
Id: 55211,
BirthTime: birthTime,
},
)
fmt.Println(string(out))
//{"id":55211,"birth_time":"2022-01-17T00:00:00+08:00"}
}
此时输出如下:
{"id":55211,"birth_time":"2022-01-17T00:00:00+08:00"}
解决方案
- 数据类型改成string, 不过不推荐这种方式, 如数据比较和校验合法性等存在难度
- 重写MarshalJSON方法
修改后代码
package main
import (
"encoding/json"
"fmt"
"time"
)
type Person struct {
Id int `json:"id"`
BirthTime time.Time `json:"birth_time"`
}
func (d Person) MarshalJSON() ([]byte, error) {
type Temp Person
return json.Marshal(&struct {
Temp
BirthTime string `json:"birth_time"`
}{
Temp: (Temp)(d),
BirthTime: d.BirthTime.Format("2006-01-02 15:04:05"),
})
}
func main() {
birthTime, _ := time.ParseInLocation("2006-01-02 15:04:05", "2022-01-17 00:00:00", time.Local)
out, _ := json.Marshal(
Person{
Id: 55211,
BirthTime: birthTime,
},
)
fmt.Println(string(out))
//{"id":55211,"birth_time":"2022-01-17 00:00:00"}
}
此时输出如下:
{"id":55211,"birth_time":"2022-01-17 00:00:00"}
gin请求时, time类型数据的参数绑定问题
golang对 time.Time
类型默认解析的日期格式是 RFC3339
标准(2006-01-02T15:04:05Z07:00
)。在使用 gin 的 shouldBindJSON
进行参数绑定时,对YYYY-MM-DD hh:mm:ss
格式的日期格式作为 time.Time 类型的值,就会引发类似于 parsing time xx as xx: cannot parse xx as xx
的报错信息。这是因为 time.Time 类型默认支持的日期格式与我们传入的格式不同,导致解析出错
解决方案
1. 自定义时间类型DateTime
type DateTime time.Time
var TimeFormats = []string{"2006-01-02 15:04:05", "20060102150405"}
2. 解析 JSON 格式数据 - UnmarshalJSON 与 MarshalJSON
在 c.ShouldBindJSON
进行参数绑定时,会调用 field.UnmarshalJSON
方法,所以需要重写 UnmarshalJSON
func (t *DateTime) UnmarshalJSON(data []byte) (err error) {
fmt.Println(string(data))
// 空值不进行解析
if len(data) == 2 {
*t = DateTime(time.Time{})
return
}
var now time.Time
for _, format := range TimeFormats {
// 指定解析的格式
if now, err = time.ParseInLocation(format, string(data), time.Local); err == nil {
*t = DateTime(now)
return
}
// 指定解析的格式
if now, err = time.ParseInLocation(`"`+format+`"`, string(data), time.Local); err == nil {
*t = DateTime(now)
return
}
}
return
}
用于接口返回时, 需要调用c.JSON
, 会调用 field.MarshalJSON
方法, 所以需要重写 MarshalJSON
func (t DateTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(TimeFormats[0])+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, TimeFormats[0])
b = append(b, '"')
return b, nil
}
demo
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type TestTime struct {
StartTime time.Time `json:"startTime" form:"startTime" binding:"required"`
EndTime time.Time `json:"endTime" form:"endTime"`
}
type DateTime time.Time
type TestDateTime struct {
StartTime DateTime `json:"startTime" form:"startTime" binding:"required"`
EndTime DateTime `json:"endTime" form:"endTime"`
}
var TimeFormats = []string{"2006-01-02 15:04:05", "20060102150405"}
func (t *DateTime) UnmarshalJSON(data []byte) (err error) {
fmt.Println(string(data))
// 空值不进行解析
if len(data) == 2 {
*t = DateTime(time.Time{})
return
}
var now time.Time
for _, format := range TimeFormats {
// 指定解析的格式
if now, err = time.ParseInLocation(format, string(data), time.Local); err == nil {
*t = DateTime(now)
return
}
// 指定解析的格式
if now, err = time.ParseInLocation(`"`+format+`"`, string(data), time.Local); err == nil {
*t = DateTime(now)
return
}
}
return
}
func (t DateTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(TimeFormats[0])+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, TimeFormats[0])
b = append(b, '"')
return b, nil
}
func (t DateTime) String() string {
return time.Time(t).Format(TimeFormats[0])
}
func main() {
router := gin.New()
router.GET("/test", func(c *gin.Context) {
var form TestTime
c.ShouldBind(&form)
c.JSON(200, gin.H{
"message": form,
})
})
router.POST("/test2", func(c *gin.Context) {
var form TestDateTime
fmt.Println(c.ShouldBind(&form))
fmt.Println(form)
c.JSON(200, gin.H{
"message": form,
})
})
router.GET("/test2", func(c *gin.Context) {
var form TestDateTime
fmt.Println(c.ShouldBind(&form))
fmt.Println(form)
c.JSON(200, gin.H{
"message": form,
})
})
s := &http.Server{
Addr: ":8000",
Handler: router,
ReadTimeout: 60,
WriteTimeout: 60,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
gorm的update问题, 可参考: https://github.com/a1029563229/Blogs/tree/master/BugFix/go/time