教程

自定义数据类型

qianmoQqianmoQ· 更新于 2026-09-23· 阅读 10 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

GORM 提供了一些接口,允许用户为 GORM 定义有良好支持的自定义数据类型,以 json 为例

实现自定义数据类型

Scanner / Valuer

自定义数据类型必须实现 Scanner 和 Valuer 接口,这样 GORM 才知道如何将其接收/保存到数据库中

例如:

type JSON json.RawMessage


func (j *JSON) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }

  result := json.RawMessage{}
  err := json.Unmarshal(bytes, &result)
  *j = JSON(result)
  return err
}


func (j JSON) Value() (driver.Value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.RawMessage(j).MarshalJSON()
}

有许多第三方包实现了 Scanner/Valuer 接口,可以与 GORM 一起使用,例如:

import (
  "github.com/google/uuid"
  "github.com/lib/pq"
)

type Post struct {
  ID     uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
  Title  string
  Tags   pq.StringArray `gorm:"type:text[]"`
}

GormDataTypeInterface

GORM 会从 tag 中的 type 读取列的数据库类型,如果未找到,则会检查该结构体是否实现了接口 GormDBDataTypeInterface 或 GormDataTypeInterface,并使用其返回值作为数据类型

type GormDataTypeInterface interface {
  GormDataType() string
}

type GormDBDataTypeInterface interface {
  GormDBDataType(*gorm.DB, *schema.Field) string
}

GormDataType 的返回值将用作通用数据类型,并可以通过 schema.Field 的 DataType 字段获取,这在编写插件或钩子时可能很有帮助,例如:

func (JSON) GormDataType() string {
  return "json"
}

type User struct {
  Attrs JSON
}

func (user User) BeforeCreate(tx *gorm.DB) {
  field := tx.Statement.Schema.LookUpField("Attrs")
  if field.DataType == "json" {
    
  }
}

GormDBDataType 通常在迁移时为当前驱动返回正确的数据类型,例如:

func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
  

  
  switch db.Dialector.Name() {
  case "mysql", "sqlite":
    return "JSON"
  case "postgres":
    return "JSONB"
  }
  return ""
}

如果结构体没有实现 GormDBDataTypeInterface 或 GormDataTypeInterface 接口,GORM 会根据结构体的第一个字段猜测其数据类型,例如,对 NullString 将使用 string

type NullString struct {
  String string 
  Valid  bool
}

type User struct {
  Name NullString 
}

GormValuerInterface

GORM 提供了 GormValuerInterface 接口,允许根据上下文从 SQL Expr 或值创建/更新记录,例如:


type GormValuerInterface interface {
  GormValue(ctx context.Context, db *gorm.DB) clause.Expr
}

从 SQL Expr 创建/更新

type Location struct {
  X, Y int
}

func (loc Location) GormDataType() string {
  return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  return clause.Expr{
    SQL:  "ST_PointFromText(?)",
    Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  }
}


func (loc *Location) Scan(v interface{}) error {
  
}

type User struct {
  ID       int
  Name     string
  Location Location
}

db.Create(&User{
  Name:     "jinzhu",
  Location: Location{X: 100, Y: 100},
})


db.Model(&User{ID: 1}).Updates(User{
  Name:  "jinzhu",
  Location: Location{X: 100, Y: 100},
})

你也可以从 map 中使用 SQL Expr 进行创建/更新,详情请查看从 SQL Expr 创建和使用 SQL 表达式更新

基于上下文的值

如果你想根据当前上下文创建或更新一个值,也可以实现 GormValuerInterface 接口,例如:

type EncryptedString struct {
  Value string
}

func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
  if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
    return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
  } else {
    db.AddError(errors.New("invalid encryption key"))
  }

  return
}

Clause 表达式

如果你想构建一些查询辅助工具,可以创建一个实现 clause.Expression 接口的结构体:

type Expression interface {
  Build(builder Builder)
}

详情请查看 JSON 和 SQL Builder,以下是使用示例:


db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))









db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))




自定义数据类型集合

我们创建了一个用于收集自定义数据类型的 GitHub 仓库 https://github.com/go-gorm/datatypes,欢迎提交 PR ;)

GitHub tag (latest SemVer)

评论

登录后参与评论

正在加载评论…