PostgreSQL的jsonb字段在Go中需用json.RawMessage或自定义struct接收,不可直接scan到string或sql.NullString;写入NULL须用nil指针,查询时应避免SELECT 配合[]interface{}。

PostgreSQL 的 jsonb 字段在 Go 中不能直接 scan 到 string
PostgreSQL 返回的 jsonb 是二进制格式,不是纯文本。如果用 Scan(&s) 扫到 string,会报错:sql: Scan error on column index 0: unsupported Scan, storing driver.Value type []uint8 into type *string。
正确做法是用 json.RawMessage 或自定义 struct 接收:
-
json.RawMessage最轻量,适合“先读再解析”,避免重复 unmarshal - 定义 struct 并用
Scan直接映射,要求字段结构稳定;否则容易 panic 或丢字段 - 别用
interface{}+json.Unmarshal,Go 的json包对interface{}解析后类型不明确(比如数字全转float64),后续取值易出错
var data json.RawMessage err := row.Scan(&data) // ✅ 正确 // 后续可多次解析:json.Unmarshal(data, &v)
用 sql.NullString 包装 jsonb 字段会失效
sql.NullString 只能处理 PostgreSQL 的 text 或 varchar 类型,对 jsonb 无效。即使字段允许 NULL,直接扫进 sql.NullString 仍会触发上面那个 []uint8 → string 的类型错误。
真正支持 NULL 的 JSONB 接收方式只有两种:
立即学习“go语言免费学习笔记(深入)”;
-
*json.RawMessage(指针,nil 表示 NULL) - 自定义类型,实现
sql.Scanner和driver.Valuer,内部用json.RawMessage存储
别为了“看起来像 NullString”强行套壳,反而增加歧义和维护成本。
写入 jsonb 时,nil、空 slice、空 map 的行为差异
PostgreSQL 的 jsonb 对 NULL 值敏感,Go 写入时不同零值表现不一致:
-
nil *json.RawMessage→ PostgreSQLNULL(✅ 预期) -
json.RawMessage([]byte("null"))→ PostgreSQLJSON null(⚠️ 注意:这是 JSON 字面量 null,不是 SQL NULL) -
json.RawMessage([]byte{})或json.RawMessage(nil)→ 报错:invalid character '}' looking for beginning of value(❌ 空字节切片非法) -
map[string]interface{}{}→ 写入{}(空对象),不是 NULL
所以判断是否写 NULL,必须靠指针是否为 nil,而不是内容是否为空。
查询含 jsonb 字段的记录时,SELECT * 容易引发 panic
如果表里有 jsonb 字段,又用 rows.Scan() 配 []interface{} 动态接收,Go 默认把 jsonb 当作 []uint8,而 []interface{} 里的元素类型是 interface{},不会自动转换——结果是 scan 成功但后续类型断言失败,panic。
稳妥做法只有两个:
- 显式声明每个字段变量类型(如
var id int, data json.RawMessage),然后Scan(&id, &data) - 用
pgx替代database/sql(它原生支持jsonb映射,无需手动处理[]uint8)
别迷信 SELECT * 在 JSONB 场景下的通用性,字段多了、类型混了,早晚踩坑。


















