必须用自定义类型实现marshaljson才能控制time.time的json输出格式,因为json标签和string选项无法改变go默认的iso8601序列化行为;正确做法是定义localtime类型并实现marshaljson/unmarshaljson方法,配合format("2006-01-02 15:04:05")达成yyyy-mm-dd hh:mm:ss格式。

直接在结构体字段上加 json 标签无法控制时间格式,必须用自定义类型 + 实现 MarshalJSON 才能稳定输出 YYYY-MM-DD HH:MM:SS 这类格式。
为什么 time.Time 字段返回带毫秒和时区的字符串
Gin 默认使用 Go 标准库序列化 time.Time,结果是 ISO8601 格式(如 "2026-09-05T08:18:22.123+08:00")。这不是前端想要的简洁格式,也不能靠结构体标签改。
- 写
json:"created_at,string"只会让 Go 尝试调用String(),但默认实现仍带时区和纳秒 -
time.Format()是运行时行为,不能在 JSON 序列化阶段自动触发 - 试图在 handler 里手动转字符串再塞进 map,会破坏结构体一致性,也不利于复用
正确做法:定义 LocalTime 类型并实现 MarshalJSON
新建 model/time_type.go,定义一个包装 time.Time 的类型,并重写其 JSON 序列化逻辑:
package model
import (
"encoding/json"
"time"
)
type LocalTime time.Time
const timeFormat = "2006-01-02 15:04:05"
func (t LocalTime) MarshalJSON() ([]byte, error) {
tt := time.Time(t)
if tt.IsZero() {
return []byte("null"), nil
}
return json.Marshal(tt.Format(timeFormat))
}
func (t *LocalTime) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
*t = LocalTime(time.Time{})
return nil
}
tt, err := time.Parse(`"`+timeFormat+`"`, string(data))
if err != nil {
return err
}
*t = LocalTime(tt)
return nil
}
- 必须检查
IsZero(),否则零值时间会被格式化成"0001-01-01 00:00:00" - 反序列化时注意引号:
time.Parse需要匹配 JSON 字符串的双引号,所以用`"`+timeFormat+`"` - 这个类型可直接用于 GORM 模型字段,只要额外实现
driver.Valuer和sql.Scanner(知识库已有完整示例)
在结构体中使用 LocalTime 替代 time.Time
把原来用 time.Time 的字段换成 model.LocalTime,并保持 json 标签不变:
type Article struct {
ID uint `json:"id"`
Title string `json:"title"`
CreatedAt model.LocalTime `json:"created_at"`
UpdatedAt model.LocalTime `json:"updated_at"`
}
- 不需要改路由或 handler 代码,
c.JSON(200, article)会自动调用你定义的MarshalJSON - 如果字段可能为空,建议用指针:
*model.LocalTime,避免零值歧义 - 不要混用:同一个结构体里别一部分用
time.Time、一部分用LocalTime,容易漏掉格式统一
注意模板渲染和数据库读写的兼容性
这个 LocalTime 类型只管 JSON 输出,但它在 HTML 模板或数据库操作中行为不同:
- HTML 模板里
{{ .CreatedAt }}会调用String()方法 —— 如果你没实现它,默认还是 ISO 格式;需补上:func (t LocalTime) String() string { return time.Time(t).Format(timeFormat) } - GORM 读写需要额外实现
Value()和Scan(),否则存进去的是完整时间戳,查出来仍是带毫秒的time.Time - 如果你只用 JSON API、不用模板和 DB,那只需
MarshalJSON+UnmarshalJSON就够了











