Go中MySQL row.Scan时如何动态选择对应结构体实现?
问题描述
我有一个MySQL表,其中某JSON列存储不同类型的配置信息,希望在调用row.Scan方法时将该列解析为对应的结构体。
模型定义
import "errors" import "encoding/json" type ServerConfig interface { Scan(value interface{}) error } type SystemSettings struct { SystemId int64 SystemName string Settings ServerConfig } type SoapConfig struct { Username string `json:"username"` Password string `json:"password"` Tenant string `json:"tenant"` BaseUrl string `json:"baseUrl"` } func (m *SoapConfig) Scan(value interface{}) error { b, ok := value.([]byte) if !ok { return errors.New("type assertion to []byte failed") } return json.Unmarshal(b, m) } type RestConfig struct { Endpoint string `json:"endpoint"` Token string `json:"token"` } func (m *RestConfig) Scan(value interface{}) error { b, ok := value.([]byte) if !ok { return errors.New("type assertion to []byte failed") } return json.Unmarshal(b, m) }
期望的仓库层用法
// ... 查询逻辑 config := &model.SystemSettings{} config.Settings = &SoapConfig{} if err = row.Scan( &config.SystemId, &config.SystemName, &config.Settings, ); err != nil { return nil, err } return config, nil
报错信息
Scan error on column index 3, name "server_config": unsupported Scan, storing driver.Value type []uint8 into type *model.ServerConfig"
希望实现根据systemName动态选择用于扫描的结构体,请问该如何解决?
解决方案
报错核心原因:ServerConfig是接口类型,数据库驱动无法直接将[]uint8(JSON列原始数据)扫描到接口指针中——驱动需要明确的具体结构体类型才能调用对应的Scan方法。以下是两种可行的解决思路:
方法一:分阶段扫描+动态实例化
先扫出基础字段和JSON原始字节,再根据systemName实例化对应配置结构体,最后完成反序列化:
// ... 查询逻辑 var systemId int64 var systemName string var configBytes []byte // 第一步:扫描基础字段和原始JSON数据 if err = row.Scan( &systemId, &systemName, &configBytes, ); err != nil { return nil, err } // 第二步:根据systemName实例化配置结构体 var settings ServerConfig switch systemName { case "soap": settings = &SoapConfig{} case "rest": settings = &RestConfig{} default: return nil, errors.New("unknown system type") } // 反序列化JSON到配置结构体 if err = json.Unmarshal(configBytes, settings); err != nil { return nil, err } // 组装最终结果 result := &SystemSettings{ SystemId: systemId, SystemName: systemName, Settings: settings, } return result, nil
方法二:给SystemSettings实现自定义Scan方法
把动态解析逻辑封装到SystemSettings的Scan方法中,实现直接扫描到目标结构体:
func (s *SystemSettings) Scan(value interface{}) error { // row.Scan传入的是列数据的切片 cols, ok := value.([]interface{}) if !ok { return errors.New("invalid scan value type") } // 解析SystemId idBytes, ok := cols[0].([]byte) if !ok { return errors.New("system_id must be []byte") } systemId, err := strconv.ParseInt(string(idBytes), 10, 64) if err != nil { return err } s.SystemId = systemId // 解析SystemName name, ok := cols[1].(string) if !ok { return errors.New("system_name must be string") } s.SystemName = name // 解析JSON配置列 configBytes, ok := cols[2].([]byte) if !ok { return errors.New("server_config must be []byte") } // 动态实例化配置结构体 var settings ServerConfig switch s.SystemName { case "soap": settings = &SoapConfig{} case "rest": settings = &RestConfig{} default: return errors.New("unknown system type: " + s.SystemName) } if err = json.Unmarshal(configBytes, settings); err != nil { return err } s.Settings = settings return nil }
使用时直接调用:
// ... 查询逻辑 result := &SystemSettings{} if err = row.Scan(result); err != nil { return nil, err } return result, nil
注意事项
- 确保
systemName与配置类型的映射关系完全匹配,避免遗漏分支 - 若JSON列可能为空,需在解析时处理
nil或空字节的情况 - 两种方法各有优劣:方法一逻辑直观,适合简单场景;方法二封装性强,适合需要复用的场景
内容的提问来源于stack exchange,提问作者Dogukan Evcil
相关产品推荐
相关产品推荐

