GORM不能直接存Go自定义类型,因其仅原生支持基础类型(如int、string、time.Time)和部分数据库类型;自定义类型须实现sql.Scanner和driver.Valuer接口,分别用于从数据库读取时反序列化、写入时序列化,否则会报错“unsupported type”或字段被忽略。

为什么 GORM 不能直接存 Go 自定义类型
GORM 默认只认识基础类型(int、string、time.Time 等)和部分数据库原生类型(如 sql.NullString)。当你定义一个结构体(比如 type UserID int64)或带方法的类型(如 type Email string),GORM 不知道怎么把它转成 SQL 值,也不清楚如何从数据库读回后还原——它会报错:unsupported type xxx, a struct 或静默忽略字段。
实现 Scanner 和 Valuer 接口是最小可行方案
这是 GORM(以及底层 database/sql)约定的序列化协议。只要你的类型实现了这两个接口,GORM 就能自动编解码:
-
Scan(src interface{}) error:从数据库读取时调用,把src(通常是[]byte或int64)赋给当前值 -
Value() (driver.Value, error):写入数据库前调用,返回可被驱动接受的原始值(如int64、string)
示例:让 type Status uint8 映射为 MySQL 的 TINYINT
type Status uint8
func (s *Status) Scan(value interface{}) error {
if value == nil {
return nil
}
switch v := value.(type) {
case int64:
*s = Status(v)
case []byte:
i, _ := strconv.ParseUint(string(v), 10, 8)
*s = Status(i)
}
return nil
}
func (s Status) Value() (driver.Value, error) {
return int64(s), nil
}
注意指针接收 vs 值接收的陷阱
如果 Scan 方法用值接收(func (s Status) Scan(...)),修改的是副本,原字段不会被更新——数据永远为空。必须用指针接收(func (s *Status) Scan(...))。
而 Value 方法通常用值接收更安全(避免 nil 指针 panic),除非你需要根据内部状态做判断。
常见错误现象:
- 结构体字段读出来一直是零值(
Scan没生效)→ 检查是否用了值接收 - 插入时报
cannot convert xxx to driver.Value→Value方法返回类型不对,确保是driver.Value(即any,但实际常用string/int64/[]byte) - JSON 字段存成
map[string]interface{}后无法 Scan → 该类型没实现Scanner,得包装一层或改用json.RawMessage
复杂类型建议封装为独立结构体 + GORM 标签控制
比如想存 IP 地址,别直接用 net.IP(它底层是 []byte,但没实现 Valuer/Scanner)。可以这样:
type IP net.IP
func (ip *IP) Scan(value interface{}) error {
if value == nil {
return nil
}
if b, ok := value.([]byte); ok {
*ip = IP(net.ParseIP(string(b)))
}
return nil
}
func (ip IP) Value() (driver.Value, error) {
if len(ip) == 0 {
return nil, nil
}
return net.IP(ip).String(), nil
}
并在模型中显式指定列类型,避免 GORM 猜错:
type User struct {
ID uint `gorm:"primaryKey"`
Addr IP `gorm:"column:addr;type:varchar(45)"`
}
type 标签告诉 GORM 用什么数据库类型,column 避免字段名冲突;不加的话,GORM 可能生成 addr bytea(PostgreSQL)或报错。
真正麻烦的不是写两个接口,而是每个自定义类型都要手动配一遍 Scan/Value,且测试覆盖容易遗漏——尤其是空值、边界值、不同数据库驱动返回格式差异(比如 PostgreSQL 的 BYTEA 是 hex-encoded []byte,MySQL 的 BLOB 是 raw bytes)。


















