datatypes.json 是 gorm v2 推荐的 json 字段类型,但需手动序列化/反序列化,不支持点语法查询、无结构体自动转换、空值语义需显式处理,适用于动态结构或整字段读写场景。

datatypes.JSON 是 GORM v2 官方推荐的 JSON 字段处理方式,但它不是“开箱即用”的万能解,用错场景或忽略细节反而会引入隐性 bug。
为什么 datatypes.JSON 不能直接赋结构体
很多人写成这样:
type Writer struct {
ID int `gorm:"primaryKey"`
Name string
Milestones datatypes.JSON `gorm:"column:milestones"`
}
// ❌ 错误用法
writer.Milestones = WriterMilestones{...} // 编译失败:cannot assign struct to datatypes.JSON
datatypes.JSON 底层是 []byte 别名,它不提供自动序列化能力。你必须手动 json.Marshal 后再赋值:
- 写入前:把结构体转成
[]byte,再转成datatypes.JSON - 读取后:先转回
[]byte,再json.Unmarshal到目标结构体 - 空值处理:如果结构体字段为
nil,需显式赋nil(否则会存空 JSON{})
datatypes.JSON 查询时无法直接点语法取值
你不能这么写查询条件:
db.Where("milestones->>'year' = ?", "2006").Find(&writers) // ✅ 可行,原生 SQL
db.Where("milestones.year = ?", "2006").Find(&writers) // ❌ GORM 不解析 JSON 内部路径
因为 datatypes.JSON 在 GORM 层只是个字节容器,不参与字段路径映射。所有 JSON 内部查询都得靠 MySQL 原生函数(->、->>、JSON_CONTAINS 等),并配合 db.Where 传原始 SQL 片段。
- 字符串提取用
->>(返回 unquoted text) - JSON 子对象提取用
->(返回 JSON type) - 数组包含判断用
JSON_CONTAINS(milestones, '"2006"', '$.year') - 注意:MySQL 5.7 不支持
JSON_EXTRACT的简写->,需确认版本
和自定义 Scanner/Valuer 比,datatypes.JSON 适合什么场景
它本质是「类型安全的字节管道」,不是「结构体代理」。适用边界很明确:
- 字段结构高度动态(比如
metadata),你根本不想定义 Go struct —— 直接用map[string]interface{}+json.Unmarshal解析即可 - 只做整字段读写,不依赖 JSON 内部字段建索引或高频查询
- 团队对 GORM 版本统一且可控(v2.2.0+ 更稳定),不需兼容老项目
- 不需要在 GORM Hooks(如 BeforeCreate)里对 JSON 内容做逻辑校验 —— 因为它不暴露结构
一旦你需要强类型约束、字段级默认值、嵌套验证或频繁按路径查询,就得退回到自定义类型方案。
最常被忽略的一点:datatypes.JSON 不会帮你做空值语义对齐。数据库存 NULL 时,Scan 进来是 nil;但如果你给它赋一个空 struct 的 json.Marshal 结果,就会存 {} —— 这在业务上可能是完全不同的含义。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











