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

    For example:

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

    1. import (
    2. "github.com/google/uuid"
    3. "github.com/lib/pq"
    4. )
    5. type Post struct {
    6. ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
    7. Title string
    8. Tags pq.StringArray `gorm:"type:text[]"`
    9. }
    1. type GormDataTypeInterface interface {
    2. GormDataType() string
    3. }
    4. type GormDBDataTypeInterface interface {
    5. GormDBDataType(*gorm.DB, *schema.Field) string
    6. }

    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 for example:

    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
    4. // returns different database type based on driver name
    5. switch db.Dialector.Name() {
    6. case "mysql", "sqlite":
    7. return "JSON"
    8. case "postgres":
    9. return "JSONB"
    10. }
    11. return ""
    12. }

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

    1. String string // use the first field's data type
    2. Valid bool
    3. }
    4. type User struct {
    5. Name NullString // data type will be string
    6. }

    Create/Update from SQL Expr

    1. type Location struct {
    2. X, Y int
    3. }
    4. func (loc Location) GormDataType() string {
    5. return "geometry"
    6. }
    7. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
    8. return clause.Expr{
    9. SQL: "ST_PointFromText(?)",
    10. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
    11. }
    12. }
    13. // Scan implements the sql.Scanner interface
    14. func (loc *Location) Scan(v interface{}) error {
    15. // Scan a value into struct from database driver
    16. }
    17. type User struct {
    18. ID int
    19. Name string
    20. Location Location
    21. }
    22. db.Create(&User{
    23. Name: "jinzhu",
    24. })
    25. db.Model(&User{ID: 1}).Updates(User{
    26. Name: "jinzhu",
    27. Location: Location{X: 100, Y: 100},
    28. })
    29. // 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 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. }
    4. func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
    5. if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
    6. return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
    7. } else {
    8. db.AddError(errors.New("invalid encryption key"))
    9. }
    10. return
    11. }

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

    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"))
    4. // MySQL
    5. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
    6. // SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL
    7. // PostgreSQL
    8. // SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
    9. // SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'
    10. db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
    11. // MySQL
    12. // SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"
    13. // 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 , pull request welcome ;)