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:
1. type JSON json.RawMessage
3. // Scan scan value into Jsonb, implements sql.Scanner interface
4. func (j *JSON) Scan(value interface{}) error {
5. bytes, ok := value.([]byte)
6. if !ok {
7. return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
8. }
10. result := json.RawMessage{}
11. err := json.Unmarshal(bytes, &result)
12. *j = JSON(result)
13. return err
14. }
16. // Value return json value, implement driver.Valuer interface
17. func (j JSON) Value() (driver.Value, error) {
18. if len(j) == 0 {
19. return nil, nil
20. }
21. return json.RawMessage(j).MarshalJSON()
22. }
There are many third party packages implement the Scanner/Valuer interface, which can be used with GORM together, for example:
1. import (
2. "github.com/google/uuid"
3. "github.com/lib/pq"
4. )
6. type Post struct {
7. ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
8. Title string
9. Tags pq.StringArray `gorm:"type:text[]"`
10. }
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
1. type GormDataTypeInterface interface {
2. GormDataType() string
3. }
5. type GormDBDataTypeInterface interface {
6. GormDBDataType(*gorm.DB, *schema.Field) string
7. }
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:
1. func (JSON) GormDataType() string {
2. return "json"
3. }
5. type User struct {
6. Attrs JSON
7. }
9. func (user User) BeforeCreate(tx *gorm.DB) {
10. field := tx.Statement.Schema.LookUpField("Attrs")
11. if field.DataType == "json" {
12. // do something
13. }
14. }
GormDBDataType usually returns the right data type for current driver when migrating, for example:
1. func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
2. // use field.Tag, field.TagSettings gets field's tags
3. // checkout https://github.com/go-gorm/gorm/blob/master/schema/field.go for all options
5. // returns different database type based on driver name
6. switch db.Dialector.Name() {
7. case "mysql", "sqlite":
8. return "JSON"
9. case "postgres":
10. return "JSONB"
11. }
12. return ""
13. }
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
1. type NullString struct {
2. String string // use the first field's data type
3. Valid bool
4. }
6. type User struct {
7. Name NullString // data type will be string
8. }
GormValuerInterface
GORM provides a GormValuerInterface interface, which can allow to create/update from SQL Expr or value based on context, for example:
1. // GORM Valuer interface
2. type GormValuerInterface interface {
3. GormValue(ctx context.Context, db *gorm.DB) clause.Expr
4. }
Create/Update from SQL Expr
1. type Location struct {
2. X, Y int
3. }
5. func (loc Location) GormDataType() string {
6. return "geometry"
7. }
9. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
10. return clause.Expr{
11. SQL: "ST_PointFromText(?)",
12. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
13. }
14. }
16. // Scan implements the sql.Scanner interface
17. func (loc *Location) Scan(v interface{}) error {
18. // Scan a value into struct from database driver
19. }
21. type User struct {
22. ID int
23. Name string
24. Location Location
25. }
27. db.Create(&User{
28. Name: "jinzhu",
29. Location: Location{X: 100, Y: 100},
30. })
31. // INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
33. db.Model(&User{ID: 1}).Updates(User{
34. Name: "jinzhu",
35. Location: Location{X: 100, Y: 100},
36. })
37. // UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
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:
1. type EncryptedString struct {
2. Value string
3. }
5. func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
6. if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
7. return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
8. } else {
9. db.AddError(errors.New("invalid encryption key"))
10. }
12. return
13. }
Clause Expression
If you want to build some query helpers, you can make a struct that implements the clause.Expression interface:
1. type Expression interface {
2. Build(builder Builder)
3. }
Checkout JSON and SQL Builder for details, the following is an example of usage:
1. // Generates SQL with clause Expression
2. db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
3. db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))
5. // MySQL
6. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
7. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL
9. // PostgreSQL
10. // SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
11. // SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'
13. db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
14. // MySQL
15. // SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"
17. // PostgreSQL
18. // SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'name') = 'jinzhu'
Customized Data Types Collections
We created a Github repo for customized data types collections <https://github.com/go-gorm/datatypes>, pull request welcome ;)
