Go-pg框架中json.RawMessage字段Scan后值为nil的问题排查
解决go-pg中json.RawMessage字段Scan后为nil的问题
问题场景
使用Go语言的PostgreSQL ORM框架go-pg(底层基于pq),定义包含json.RawMessage类型字段的Item结构体,从PostgreSQL加载数据时,该字段经Scan后始终输出nil。尝试两种方案均未解决:
- 将
json.RawMessage别名并实现Scan/Value方法,问题依旧; - 封装包含
*json.RawMessage字段的JSON结构体,Scan方法内赋值后j.raw的值在外部丢失,Marshal后data字段仍为nil。
错误原因分析
- 第一种别名方案逻辑本身正确,但可能存在go-pg结构体标签配置错误;
- 第二种封装方案的核心问题是
Scan方法使用值接收者:调用Scan时会传入结构体副本,方法内对j.raw的修改仅作用于副本,原结构体字段不会被更新,导致外部无法获取赋值后的值。
简洁解决办法
直接使用json.RawMessage的别名类型,配合指针接收者实现Scan/Value方法,并正确配置go-pg的结构体标签:
import ( "database/sql/driver" "encoding/json" "fmt" ) // JSON 别名json.RawMessage,实现sql.Scanner和driver.Valuer接口 type JSON json.RawMessage // Scan 指针接收者,确保修改原结构体字段 func (f *JSON) Scan(value interface{}) error { if value == nil { *f = nil return nil } switch v := value.(type) { case []byte: *f = append((*f)[0:0], v...) // 复制数据,避免引用临时底层数组 case string: *f = append((*f)[0:0], v...) default: return fmt.Errorf("无法将类型%T扫描到JSON字段", value) } return nil } // Value 值接收者,返回JSON字符串 func (f JSON) Value() (driver.Value, error) { if len(f) == 0 { return nil, nil } return string(f), nil } // 定义Item结构体,sql标签指定jsonb对应PostgreSQL的jsonb类型 type Item struct { ID string `sql:"id,pk"` Data JSON `sql:"data,jsonb" json:"data"` }
使用示例
func main() { item := &Item{} err := db.Model(item).Where("id = ?", "test").Select() if err != nil { panic(err) } fmt.Println(item.Data) // 正常输出JSON数据 bytes, err := json.Marshal(item) if err != nil { panic(err) } fmt.Println(string(bytes)) // data字段正常序列化 }
关键说明
- 必须使用指针接收者实现
Scan方法,才能直接修改原结构体的字段值,避免副本导致的赋值丢失; - go-pg结构体标签中的
jsonb指定字段对应PostgreSQL的jsonb类型,确保ORM正确处理JSON数据; Scan方法中用append((*f)[0:0], v...)复制数据,避免引用数据库返回的临时底层数组,防止后续数据被覆盖。
内容的提问来源于stack exchange,提问作者AAA
相关产品推荐
相关产品推荐

