You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 20:20:18