InfoSphere/GORM 中文版/ 返回书籍
Tutorials

Customize Data Types

qianmoQqianmoQ· 更新于 2026-09-15· 阅读 14 分钟· 0 次阅读

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

GORM provides few interfaces that allow users to define well-supported customized data types for GORM, takes json as an example

Implements Customized Data Type

Scanner / Valuer

The customized data type has to implement the Scanner and Valuer interfaces, so GORM knowns to how to receive/save it into the database

For example:

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()
}

There are many third party packages implement the Scanner/Valuer interface, which can be used with GORM together, for example:

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 will read column’s database type from tag type, if not found, will check if the struct implemented interface GormDBDataTypeInterface or GormDataTypeInterface and will use its result as data type

type GormDataTypeInterface interface {
  GormDataType() string
}

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

The result of GormDataType will be used as the general data type and can be obtained from schema.Field‘s field DataType, which might be helpful when writing plugins or hooks for example:

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 usually returns the right data type for current driver when migrating, for example:

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

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

If the struct hasn’t implemented the GormDBDataTypeInterface or GormDataTypeInterface interface, GORM will guess its data type from the struct’s first field, for example, will use string for NullString

type NullString struct {
  String string 
  Valid  bool
}

type User struct {
  Name NullString 
}

GormValuerInterface

GORM provides a GormValuerInterface interface, which can allow to create/update from SQL Expr or value based on context, for example:


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

Create/Update from 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},
})

You can also create/update with SQL Expr from map, checkout Create From SQL Expr and Update with SQL Expression for details

Value based on Context

If you want to create or update a value depends on current context, you can also implements the GormValuerInterface interface, for example:

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 Expression

If you want to build some query helpers, you can make a struct that implements the clause.Expression interface:

type Expression interface {
  Build(builder Builder)
}

Checkout JSON and SQL Builder for details, the following is an example of usage:


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"))




Customized Data Types Collections

We created a Github repo for customized data types collections https://github.com/go-gorm/datatypes, pull request welcome ;)

GitHub tag (latest SemVer)

评论

登录后参与评论

正在加载评论…