gorm 自定义时间、字符串数组类型

GORM 是GO语言中一款强大友好的ORM框架,但在使用过程中内置的数据类型不能满足以下两个需求,如下:

  1. time.Time类型返回的是 2023-10-03T09:12:08.53528+08:00这种字符串格式,需要额外处理,我们更希望默认的是是2023-10-03 09:12:08这种可读性更高的格式
  2. 有些数据字段需要存储数组形式,如下Article Tags字段希望保存不确定个字符串。直接保存会提示[error] unsupported data type: &[]

官方提供了 ScannerValuer两个接口,来满足自定义数据的存储、提取,本文记录上述两种结构解决方法。

type Article struct {
	Tags  []string
}

自定义时间类型

自定义时间类型需满足以下两个需求:

  • 返回2006-01-02 15:04:05格式
  • CreatedAtUpdatedAt使用时能按GORM规范自动填充当前时间

默认的time.Time是能被gorm自动存储,取出到结构体的,返回2023-10-03T09:12:08.53528+08:00格式原因是在于json序列化时,这里解决方案是自定义一个数据结构,添加JSON Marshal接口,但是自定义的数据类型gorm不能识别,所以要额外添加gorm ScannerValuer两个接口

type CustomTime time.Time

// GORM Scanner 接口, 从数据库读取到类型
func (t *CustomTime) Scan(value any) error {

	if v, ok := value.(time.Time); !ok {
		return errors.Errorf("failed to unmarshal CustomTime value: %v", value)
	} else {
		*t = CustomTime(v)
		return nil
	}
}

// GORM Valuer 接口, 保存到数据库
func (t CustomTime) Value() (driver.Value, error) {
	if time.Time(t).IsZero() {
		return nil, nil
	}
	return time.Time(t), nil
}

// JSON Marshal接口,CustomTime结构体转换为json字符串
func (t *CustomTime) MarshalJSON() ([]byte, error) {
	t2 := time.Time(*t)
	return []byte(fmt.Sprintf(`"%v"`, t2.Format("2006-01-02 15:04:05"))), nil
}

自定义字符串数组

代码比较简单,直接定义一个类型实现 ScannerValuer两个接口,使用中将列定义为Strings类型即可

type Strings []string

func (s *Strings) Scan(value any) error {
	v, _ := value.(string)
	return json.Unmarshal([]byte(v), s)
}
func (s Strings) Value() (driver.Value, error) {
	b, err := json.Marshal(s)
	return string(b), err
}

测试与完整代码

测试代码

package main

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"time"

	"github.com/pkg/errors"
	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

type Strings []string

func (s *Strings) Scan(value any) error {
	v, _ := value.(string)
	return json.Unmarshal([]byte(v), s)
}
func (s Strings) Value() (driver.Value, error) {
	b, err := json.Marshal(s)
	return string(b), err
}

type CustomTime time.Time

// GORM Scanner 接口, 从数据库读取到类型
func (t *CustomTime) Scan(value any) error {

	if v, ok := value.(time.Time); !ok {
		return errors.Errorf("failed to unmarshal CustomTime value: %v", value)
	} else {
		*t = CustomTime(v)
		return nil
	}
}

// GORM Valuer 接口, 保存到数据库
func (t CustomTime) Value() (driver.Value, error) {
	if time.Time(t).IsZero() {
		return nil, nil
	}
	return time.Time(t), nil
}

// JSON Marshal接口,CustomTime结构体转换为json字符串
func (t *CustomTime) MarshalJSON() ([]byte, error) {
	t2 := time.Time(*t)
	return []byte(fmt.Sprintf(`"%v"`, t2.Format("2006-01-02 15:04:05"))), nil
}

// fmt.Printf, 【可选方法】
func (t CustomTime) String() string {
	return time.Time(t).Format("2006-01-02 15:04:05")
}

type Article struct {
	ID        uint `gorm:"primaryKey"`
	Tags      Strings
	CreatedAt CustomTime
	UpdatedAt CustomTime
}

func main() {
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

	db.AutoMigrate(&Article{})
	db.Create(&Article{Tags: []string{"go", "java"}})

	var article Article

	db.Last(&article)
	fmt.Printf("article is: %v\n", article)
	b, _ := json.Marshal(&article)
	fmt.Printf("article json is: %s\n", string(b))

	time.Sleep(time.Second * 30)
	article.Tags = append(article.Tags, "python")
	db.Save(&article)

	db.Last(&article)
	fmt.Printf("updated article is: %v\n", article)
	b, _ = json.Marshal(&article)
	fmt.Printf("updated article json is: %s\n", string(b))
}

测试结果

字符串数组

在这里插入图片描述

自定义时间,可以看到满足2006-01-02 15:04:05格式输出,以及时间自动添加和更新
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值