Golang操作MySQL json字段优雅写法

背景

如下soc_manpower_shift_forecast_tab的version保存一个json格式的结构体。

sql 复制代码
CREATE TABLE `soc_manpower_shift_forecast_tab` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `station_id` bigint(20) unsigned NOT NULL DEFAULT '0',
 `operator` varchar(128) NOT NULL DEFAULT '',
 `forecast_date` int(10) unsigned NOT NULL DEFAULT '0',
 `solution_type` tinyint(4) unsigned NOT NULL DEFAULT '0',
 `version` text NOT NULL,
 `ctime` int(10) unsigned NOT NULL DEFAULT '0',
 `mtime` int(10) unsigned NOT NULL DEFAULT '0',
 PRIMARY KEY (`id`),
 unique KEY `uniq_station_id_solution_type_date` (`station_id`,`solution_type`, `forecast_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

一般的写法是golang定义的结构体如下,Version是string类型,使用时json反序列化成结构体,写入数据库时把结构体序列化为string。

go 复制代码
type SocManpowerShiftForecast struct {
	ID           int64        `gorm:"column:id;primaryKey" json:"id"`
	StationID    int64        `gorm:"column:station_id" json:"station_id"`
	Operator     string       `gorm:"column:operator" json:"operator"`
	ForecastDate int64        `gorm:"column:forecast_date" json:"forecast_date"` // 使用更友好的时间类型
	SolutionType int64        `gorm:"column:solution_type" json:"solution_type"`
	Version      string       `gorm:"column:version" json:"version"` // JSON 文本
	Ctime        int64        `gorm:"column:ctime" json:"ctime"`
	Mtime        int64        `gorm:"column:mtime" json:"mtime"`
}

优雅写法

go 复制代码
type VersionInfo struct {
	ShiftList []float64 `json:"shift_list"`
}

func (info *VersionInfo) Scan(value interface{}) error {
	bs, ok := value.([]byte)
	if !ok {
		return errors.New("value is not []byte")
	}
	return json.Unmarshal(bs, info)
}

func (info *VersionInfo) Value() (driver.Value, error) {
	return json.Marshal(info)
}

Version结构体实现Scan和Value方法

go 复制代码
type SocManpowerShiftForecast struct {
	ID           int64        `gorm:"column:id;primaryKey" json:"id"`
	StationID    int64        `gorm:"column:station_id" json:"station_id"`
	Operator     string       `gorm:"column:operator" json:"operator"`
	ForecastDate int64        `gorm:"column:forecast_date" json:"forecast_date"` // 使用更友好的时间类型
	SolutionType int64        `gorm:"column:solution_type" json:"solution_type"`
	Version      *VersionInfo `gorm:"column:version" json:"version"` // JSON 文本
	Ctime        int64        `gorm:"column:ctime" json:"ctime"`
	Mtime        int64        `gorm:"column:mtime" json:"mtime"`
}

数据库表结构体直接使用指针结构体即可,完整测试代码如下:

go 复制代码
package mysql

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"example.com/m/test/testutil"
	"example.com/m/util/stringutil"
	"fmt"
	. "github.com/smartystreets/goconvey/convey"
	"testing"
)

const SocManpowerShiftForecastTabName = "soc_manpower_shift_forecast_tab"

type SocManpowerShiftForecast struct {
	ID           int64        `gorm:"column:id;primaryKey" json:"id"`
	StationID    int64        `gorm:"column:station_id" json:"station_id"`
	Operator     string       `gorm:"column:operator" json:"operator"`
	ForecastDate int64        `gorm:"column:forecast_date" json:"forecast_date"` // 使用更友好的时间类型
	SolutionType int64        `gorm:"column:solution_type" json:"solution_type"`
	Version      *VersionInfo `gorm:"column:version" json:"version"` // JSON 文本
	Ctime        int64        `gorm:"column:ctime" json:"ctime"`
	Mtime        int64        `gorm:"column:mtime" json:"mtime"`
}

type VersionInfo struct {
	ShiftList []float64 `json:"shift_list"`
}

func (info *VersionInfo) Scan(value interface{}) error {
	bs, ok := value.([]byte)
	if !ok {
		return errors.New("value is not []byte")
	}
	return json.Unmarshal(bs, info)
}

func (info *VersionInfo) Value() (driver.Value, error) {
	return json.Marshal(info)
}

func Test_ScanValue(t *testing.T) {
	Convey("ScanValue", t, func() {
		Convey("ScanValue test1", func() {
			ctx := testutil.NewContext(testutil.NewContextRequest{})
			temp := &SocManpowerShiftForecast{
				ID:           0,
				StationID:    102,
				Operator:     "kf",
				ForecastDate: 1751299200,
				SolutionType: 1,
				Version: &VersionInfo{
					ShiftList: []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
				},
				Ctime: 1751299200,
				Mtime: 1751299200,
			}
			db := testutil.GetDBCommon(ctx)
			err := db.Table(SocManpowerShiftForecastTabName).Create(temp).Error
			So(err, ShouldEqual, nil)
			var res []*SocManpowerShiftForecast
			err = db.Table(SocManpowerShiftForecastTabName).Find(&res).Error
			So(err, ShouldEqual, nil)
			fmt.Println(stringutil.Object2String(res))
		})
	})
}

日志如下:

无论是写入还是读取都没有问题

powershell 复制代码
2025/07/12 22:21:23 /Users/workspace/go-utils/test/mysql/mysql_test.go:60
[5.581ms] [rows:1] INSERT INTO `soc_manpower_shift_forecast_tab` (`station_id`,`operator`,`forecast_date`,`solution_type`,`version`,`ctime`,`mtime`) VALUES (102,'kf',1751299200,1,'{"shift_list":[1,2,3,4,5,6]}',1751299200,1751299200)
.
相关推荐
噜哥带你玩转 AI 大模型1 小时前
面试加分项!CASE WHEN 两种写法、CTE 公共表表达式、四大类窗口函数,老哥一次讲透
mysql·窗口函数·cte·sql 进阶
bksczm2 小时前
InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer
数据库·mysql
招风的黑耳2 小时前
SpringBoot+Vue+MySQL 民宿在线预定平台 完整可运行版(源码+文档)
vue.js·spring boot·mysql
.痞老板.4 小时前
mysql初阶--数据库设计
数据库·mysql·oracle
利威尔xu5 小时前
Go map 底层崩溃:3 个 goroutine 并发写同一张 map,fatal error 崩掉整个服务
golang·源码·map·并发·后端开发
Decay__6 小时前
SQL Server 数据库全栈运维实战系列 — 合集索引(24 篇)
运维·数据库·sql·mysql·架构
Thanks_ks6 小时前
SQL 的 GROUP BY 与聚合函数:从会写 SELECT 到会做统计
数据库·sql·mysql·group by·数据统计·聚合函数·having
Decay__14 小时前
12_MySQL InnoDB 内存与锁调优 —— Buffer Pool、redo log、隔离级别与锁等待
运维·数据库·sql·mysql·架构
苏宸啊19 小时前
MySQL(一)
数据库·mysql
2603_9696948519 小时前
JDBC 基础学习笔记:API 核心原理解析与 CRUD 代码实操
数据库·笔记·mysql·学习方法·database·sql注入