自定义的数据类型必须实现
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[]"`
GORM 会从 type
标签 中读取字段的数据库类型,如果找不到,则会检查该结构体是否实现了 GormDBDataTypeInterface
或 GormDataTypeInterface
接口,然后使用接口返回值作为数据类型
type GormDataTypeInterface interface {
GormDataType() string
type GormDBDataTypeInterface interface {
GormDBDataType(*gorm.DB, *schema.Field) string
GormDataType
的结果用于生成通用数据类型,也可以通过 schema.Field
的 DataType
字段得到。这在 编写插件 或者 hook 时可能会有用,例如:
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 ""
如果 struct 没有实现 GormDBDataTypeInterface
或 GormDataTypeInterface
接口,GORM 会根据 struct 第一个字段推测其数据类型,例如:会为 NullString
使用 string
type NullString struct {
String string
Valid bool
type User struct {
Name NullString
GORM 提供了 GormValuerInterface
接口,支持使用 SQL 表达式或基于 context 的值进行 create/update,例如:
type GormValuerInterface interface {
GormValue(ctx context.Context, db *gorm.DB) clause.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",
Point: Point{X: 100, Y: 100},
你也可以根据 SQL 表达式进行 create/update,查看 Create From SQL Expr 和 Update with SQL Expression 获取详情
如果你想创建或更新一个依赖于当前 context 的值,你也可以实现 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
如果你想构建一些查询 helper,你可以让 struct 实现 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/datatype,非常欢迎同学们的 pull request ;)