Serializer is an extensible interface that allows to customize how to serialize and deserialize data with database.

GORM provides some default serializers: json, gob, unixtime, here is a quick example of how to use it.

  1. type User struct {
  2. Name []byte `gorm:"serializer:json"`
  3. Roles Roles `gorm:"serializer:json"`
  4. Contracts map[string]interface{} `gorm:"serializer:json"`
  5. JobInfo Job `gorm:"type:bytes;serializer:gob"`
  6. CreatedTime int64 `gorm:"serializer:unixtime;type:time"` // store int as datetime into database
  7. }
  8. type Roles []string
  9. type Job struct {
  10. Title string
  11. Location string
  12. IsIntern bool
  13. }
  14. createdAt := time.Date(2020, 1, 1, 0, 8, 0, 0, time.UTC)
  15. data := User{
  16. Name: []byte("jinzhu"),
  17. Roles: []string{"admin", "owner"},
  18. Contracts: map[string]interface{}{"name": "jinzhu", "age": 10},
  19. CreatedTime: createdAt.Unix(),
  20. JobInfo: Job{
  21. Title: "Developer",
  22. Location: "NY",
  23. IsIntern: false,
  24. },
  25. }
  26. DB.Create(&data)
  27. // INSERT INTO `users` (`name`,`roles`,`contracts`,`job_info`,`created_time`) VALUES
  28. // ("\"amluemh1\"","[\"admin\",\"owner\"]","{\"age\":10,\"name\":\"jinzhu\"}",<gob binary>,"2020-01-01 00:08:00")
  29. var result User
  30. DB.First(&result, "id = ?", data.ID)
  31. // result => User{
  32. // Name: []byte("jinzhu"),
  33. // Roles: []string{"admin", "owner"},
  34. // Contracts: map[string]interface{}{"name": "jinzhu", "age": 10},
  35. // CreatedTime: createdAt.Unix(),
  36. // JobInfo: Job{
  37. // Title: "Developer",
  38. // Location: "NY",
  39. // IsIntern: false,
  40. // },
  41. // }
  42. DB.Where(User{Name: []byte("jinzhu")}).Take(&result)
  43. // SELECT * FROM `users` WHERE `users`.`name` = "\"amluemh1\"

Register Serializer

A Serializer needs to implement how to serialize and deserialize data, so it requires to implement the the following interface

  1. import "gorm.io/gorm/schema"
  2. type SerializerInterface interface {
  3. Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) error
  4. SerializerValuerInterface
  5. }
  6. type SerializerValuerInterface interface {
  7. Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error)
  8. }

For example, the default JSONSerializer is implemented like:

  1. // JSONSerializer json serializer
  2. type JSONSerializer struct {
  3. }
  4. // Scan implements serializer interface
  5. func (JSONSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
  6. fieldValue := reflect.New(field.FieldType)
  7. if dbValue != nil {
  8. var bytes []byte
  9. switch v := dbValue.(type) {
  10. case []byte:
  11. bytes = v
  12. case string:
  13. bytes = []byte(v)
  14. default:
  15. return fmt.Errorf("failed to unmarshal JSONB value: %#v", dbValue)
  16. }
  17. err = json.Unmarshal(bytes, fieldValue.Interface())
  18. }
  19. field.ReflectValueOf(ctx, dst).Set(fieldValue.Elem())
  20. return
  21. }
  22. // Value implements serializer interface
  23. func (JSONSerializer) Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
  24. return json.Marshal(fieldValue)
  25. }

And registered with the following code:

  1. schema.RegisterSerializer("json", JSONSerializer{})

After registering a serializer, you can use it with the serializer tag, for example:

  1. type User struct {
  2. Name []byte `gorm:"serializer:json"`
  3. }

Customized Serializer Type

You can use a registered serializer with tags, you are also allowed to create a customized struct that implements the above SerializerInterface and use it as a field type directly, for example:

  1. type EncryptedString string
  2. // ctx: contains request-scoped values
  3. // field: the field using the serializer, contains GORM settings, struct tags
  4. // dst: current model value, `user` in the below example
  5. // dbValue: current field's value in database
  6. func (es *EncryptedString) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) (err error) {
  7. switch value := dbValue.(type) {
  8. case []byte:
  9. *es = EncryptedString(bytes.TrimPrefix(value, []byte("hello")))
  10. case string:
  11. *es = EncryptedString(strings.TrimPrefix(value, "hello"))
  12. default:
  13. return fmt.Errorf("unsupported data %#v", dbValue)
  14. }
  15. return nil
  16. }
  17. // ctx: contains request-scoped values
  18. // field: the field using the serializer, contains GORM settings, struct tags
  19. // dst: current model value, `user` in the below example
  20. // fieldValue: current field's value of the dst
  21. func (es EncryptedString) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
  22. return "hello" + string(es), nil
  23. }
  24. type User struct {
  25. gorm.Model
  26. Password EncryptedString
  27. }
  28. data := User{
  29. Password: EncryptedString("pass"),
  30. }
  31. DB.Create(&data)
  32. // INSERT INTO `serializer_structs` (`password`) VALUES ("hellopass")
  33. var result User
  34. DB.First(&result, "id = ?", data.ID)
  35. // result => User{
  36. // Password: EncryptedString("pass"),
  37. // }
  38. DB.Where(User{Password: EncryptedString("pass")}).Take(&result)
  39. // SELECT * FROM `users` WHERE `users`.`password` = "hellopass"