贝利信息

如何在 GORM 中将嵌入结构体以 JSON 形式存储为单字段

日期:2026-01-19 00:00 / 作者:心靈之曲

本文介绍如何让 gorm 将 go 嵌入结构体(如 geopoint)序列化为 json 字符串并存入数据库单列,避免默认的关联建表行为,适用于地理坐标、配置对象等场景。

在使用 GORM 定义模型时,若直接嵌入一个结构体(如 GeoPoint),GORM 默认会将其识别为关联关系或嵌入字段,并尝试生成对应子表或展开为多个列——这往往不符合实际需求。例如,你希望将 Lat 和 Lon 作为一个逻辑整体以 JSON 格式(如 {"Lat":39.9042,"Lon":116.4074})存入数据库的单一 point TEXT 或 JSON 类型字段中,而非拆成两列或新建表。

实现该目标的核心思路是:让嵌入结构体实现 GORM 的 Scanner 和 Valuer 接口,从而接管其数据库读写逻辑,完成自动 JSON 序列化与反序列化。

以下是一个完整、可直接复用的实践示例:

import (
    "database/sql/driver"
    "encoding/json"
    "gorm.io/gorm"
)

// GeoPoint 表示经纬度坐标点
type GeoPoint struct {
    Lat float64 `json:"lat"`
    Lon float64 `json:"lon"

` } // PointJSON 是用于 GORM 存储的包装类型,实现 Scanner & Valuer type PointJSON struct { *GeoPoint } // Scan 实现 sql.Scanner 接口:从数据库读取 []byte 并反序列化为 GeoPoint func (p *PointJSON) Scan(src interface{}) error { if src == nil { p.GeoPoint = nil return nil } b, ok := src.([]byte) if !ok { return fmt.Errorf("cannot scan %T into PointJSON", src) } var gp GeoPoint if err := json.Unmarshal(b, &gp); err != nil { return err } p.GeoPoint = &gp return nil } // Value 实现 driver.Valuer 接口:将 GeoPoint 序列化为 JSON 字符串写入数据库 func (p PointJSON) Value() (driver.Value, error) { if p.GeoPoint == nil { return nil, nil } return json.Marshal(p.GeoPoint) } // A 是主模型,其中 point 以 JSON 形式存储于单列 type A struct { ID uint `gorm:"primaryKey"` Name string `gorm:"size:100"` Point PointJSON `gorm:"column:point;type:json"` // 推荐使用 type:json(MySQL 5.7+/PostgreSQL),兼容性更好;若需兼容旧版 MySQL,可用 type:longtext }

关键说明:

⚠️ 注意事项:

通过该方法,你既能保持 Go 代码中清晰的结构化表达,又能在数据库层面实现扁平化、可维护的单字段存储,是 GORM 中处理“逻辑嵌入、物理扁平”场景的标准实践。