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

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. }

9. type Roles []string

11. type Job struct {
12. Title    string
13. Location string
14. IsIntern bool
15. }

17. createdAt := time.Date(2020, 1, 1, 0, 8, 0, 0, time.UTC)
18. data := User{
19. Name:        []byte("jinzhu"),
20. Roles:       []string{"admin", "owner"},
21. Contracts:   map[string]interface{}{"name": "jinzhu", "age": 10},
22. CreatedTime: createdAt.Unix(),
23. JobInfo: Job{
24. Title:    "Developer",
25. Location: "NY",
26. IsIntern: false,
27. },
28. }

30. DB.Create(&data)
31. // INSERT INTO `users` (`name`,`roles`,`contracts`,`job_info`,`created_time`) VALUES
32. //   ("\"amluemh1\"","[\"admin\",\"owner\"]","{\"age\":10,\"name\":\"jinzhu\"}",<gob binary>,"2020-01-01 00:08:00")

34. var result User
35. DB.First(&result, "id = ?", data.ID)
36. // result => User{
37. //   Name:        []byte("jinzhu"),
38. //   Roles:       []string{"admin", "owner"},
39. //   Contracts:   map[string]interface{}{"name": "jinzhu", "age": 10},
40. //   CreatedTime: createdAt.Unix(),
41. //   JobInfo: Job{
42. //     Title:    "Developer",
43. //     Location: "NY",
44. //     IsIntern: false,
45. //   },
46. // }

48. DB.Where(User{Name: []byte("jinzhu")}).Take(&result)
49. // 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"

3. type SerializerInterface interface {
4. Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) error
5. SerializerValuerInterface
6. }

8. type SerializerValuerInterface interface {
9. Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error)
10. }

For example, the default JSONSerializer is implemented like:

1. // JSONSerializer json serializer
2. type JSONSerializer struct {
3. }

5. // Scan implements serializer interface
6. func (JSONSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
7. fieldValue := reflect.New(field.FieldType)

9. if dbValue != nil {
10. var bytes []byte
11. switch v := dbValue.(type) {
12. case []byte:
13. bytes = v
14. case string:
15. bytes = []byte(v)
16. default:
17. return fmt.Errorf("failed to unmarshal JSONB value: %#v", dbValue)
18. }

20. err = json.Unmarshal(bytes, fieldValue.Interface())
21. }

23. field.ReflectValueOf(ctx, dst).Set(fieldValue.Elem())
24. return
25. }

27. // Value implements serializer interface
28. func (JSONSerializer) Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
29. return json.Marshal(fieldValue)
30. }

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

3. // ctx: contains request-scoped values
4. // field: the field using the serializer, contains GORM settings, struct tags
5. // dst: current model value, `user` in the below example
6. // dbValue: current field's value in database
7. func (es *EncryptedString) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) (err error) {
8. switch value := dbValue.(type) {
9. case []byte:
10. *es = EncryptedString(bytes.TrimPrefix(value, []byte("hello")))
11. case string:
12. *es = EncryptedString(strings.TrimPrefix(value, "hello"))
13. default:
14. return fmt.Errorf("unsupported data %#v", dbValue)
15. }
16. return nil
17. }

19. // ctx: contains request-scoped values
20. // field: the field using the serializer, contains GORM settings, struct tags
21. // dst: current model value, `user` in the below example
22. // fieldValue: current field's value of the dst
23. func (es EncryptedString) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
24. return "hello" + string(es), nil
25. }

27. type User struct {
28. gorm.Model
29. Password EncryptedString
30. }

32. data := User{
33. Password: EncryptedString("pass"),
34. }

36. DB.Create(&data)
37. // INSERT INTO `serializer_structs` (`password`) VALUES ("hellopass")

39. var result User
40. DB.First(&result, "id = ?", data.ID)
41. // result => User{
42. //   Password: EncryptedString("pass"),
43. // }

45. DB.Where(User{Password: EncryptedString("pass")}).Take(&result)
46. // SELECT * FROM `users` WHERE `users`.`password` = "hellopass"