本文介绍如何让 gorm 将 go 嵌入结构体(如地理坐标)序列化为单字段(如 json),而非创建独立关联表,通过自定义 `scan` 和 `value` 方法实现透明的 json 编解码。
在使用 GORM 时,若直接将结构体(如 GeoPoint)作为字段嵌入模型(如 A),GORM 默认会将其识别为关联关系,并尝试创建外键或新表——这与期望的「扁平化存储为单个 JSON 字段」相悖。解决该问题的核心思路是:让 GORM 将该字段视为普通数据库列(如 TEXT 或 JSON 类型),并通过实现 driver.Valuer 和 sql.Scanner 接口,自动完成结构体 ↔ JSON 字符串的双向转换。
以下是一个完整、可运行的实践示例,以地理坐标 GeoPoint 为例:
import (
"database/sql/driver"
"encoding/json"
"gorm.io/gorm"
)
type GeoPoint struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
}
// 实现 sql.Scanner 接口:从数据库读取时反序列化 JS
ON
func (p *GeoPoint) Scan(value interface{}) error {
if value == nil {
return nil
}
b, ok := value.([]byte)
if !ok {
return fmt.Errorf("cannot scan %T into GeoPoint", value)
}
return json.Unmarshal(b, p)
}
// 实现 driver.Valuer 接口:写入数据库前序列化为 JSON 字符串
func (p GeoPoint) Value() (driver.Value, error) {
return json.Marshal(p)
}
// 模型定义:嵌入 GeoPoint 作为普通字段(非关联)
type A struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Point GeoPoint `gorm:"column:point;type:json"` // PostgreSQL 推荐用 type:json;MySQL 8.0+ 同样支持;旧版 MySQL 可用 type:longtext
}✅ 关键要点说明:
⚠️ 注意事项:
通过该方式,A 表仅包含 id, name, point 三列,其中 point 存储形如 {"lat":39.9042,"lon":116.4074} 的 JSON 字符串,完全满足嵌入结构体扁平化存储的需求,同时保持代码简洁与数据一致性。