Of course, you might have already guessed that the gconv module actually uses reflection for conversions involving complex types such as struct. While it provides a great convenience for developers, it does come at the cost of performance. For struct conversions, if developers have clearly defined conversion rules and are concerned about performance costs, they can implement the UnmarshalValue interface for specific structs to achieve custom conversion. When using the gconv module for conversion, whether the struct is directly the conversion object or a property of the conversion object, gconv will automatically recognize the implemented UnmarshalValue interface and use it for type conversion instead of using reflection.

Type Conversion - Interface - 图1tip

Common deserialization interfaces in the standard library, such as UnmarshalText(text []byte) error, are also supported, and they are used in a similar manner to UnmarshalValue, with different parameters.

Interface Definition

  1. // apiUnmarshalValue is the interface for custom defined types customizing value assignment.
  2. // Note that only pointer can implement interface apiUnmarshalValue.
  3. type apiUnmarshalValue interface {
  4. UnmarshalValue(interface{}) error
  5. }

As you can see, custom types can implement the UnmarshalValue method for custom type conversion. The input parameter here is of interface{} type, and developers can use type assertions or other methods for type conversion in practical use cases.

Type Conversion - Interface - 图2warning

It’s important to note that because the UnmarshalValue type conversion modifies the properties of the current object, the receiver of the interface implementation must be a pointer type.

Correct interface implementation example (using pointer receiver):

  1. func (c *Receiver) UnmarshalValue(interface{}) error

Incorrect interface implementation example (using value receiver):

  1. func (c Receiver) UnmarshalValue(interface{}) error

Usage Examples

1. Custom Data Table Query Result struct Conversion

Data table structure:

  1. CREATE TABLE `user` (
  2. id bigint unsigned NOT NULL AUTO_INCREMENT,
  3. passport varchar(45),
  4. password char(32) NOT NULL,
  5. nickname varchar(45) NOT NULL,
  6. create_time timestamp NOT NULL,
  7. PRIMARY KEY (id)
  8. ) ;

Example code:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/v2/container/garray"
  5. "github.com/gogf/gf/v2/database/gdb"
  6. "github.com/gogf/gf/v2/errors/gerror"
  7. "github.com/gogf/gf/v2/frame/g"
  8. "github.com/gogf/gf/v2/os/gtime"
  9. "reflect"
  10. )
  11. type User struct {
  12. Id int
  13. Passport string
  14. Password string
  15. Nickname string
  16. CreateTime *gtime.Time
  17. }
  18. // Implement UnmarshalValue interface for custom struct conversion
  19. func (user *User) UnmarshalValue(value interface{}) error {
  20. if record, ok := value.(gdb.Record); ok {
  21. *user = User{
  22. Id: record["id"].Int(),
  23. Passport: record["passport"].String(),
  24. Password: "",
  25. Nickname: record["nickname"].String(),
  26. CreateTime: record["create_time"].GTime(),
  27. }
  28. return nil
  29. }
  30. return gerror.Newf(`unsupported value type for UnmarshalValue: %v`, reflect.TypeOf(value))
  31. }
  32. func main() {
  33. var (
  34. err error
  35. users []*User
  36. )
  37. array := garray.New(true)
  38. for i := 1; i <= 10; i++ {
  39. array.Append(g.Map{
  40. "id": i,
  41. "passport": fmt.Sprintf(`user_%d`, i),
  42. "password": fmt.Sprintf(`pass_%d`, i),
  43. "nickname": fmt.Sprintf(`name_%d`, i),
  44. "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
  45. })
  46. }
  47. // Insert data
  48. _, err = g.Model("user").Data(array).Insert()
  49. if err != nil {
  50. panic(err)
  51. }
  52. // Query data
  53. err = g.Model("user").Order("id asc").Scan(&users)
  54. if err != nil {
  55. panic(err)
  56. }
  57. g.Dump(users)
  58. }

After execution, the terminal output:

  1. [
  2. {
  3. Id: 1,
  4. Passport: "user_1",
  5. Password: "",
  6. Nickname: "name_1",
  7. CreateTime: "2018-10-24 10:00:00",
  8. },
  9. {
  10. Id: 2,
  11. Passport: "user_2",
  12. Password: "",
  13. Nickname: "name_2",
  14. CreateTime: "2018-10-24 10:00:00",
  15. },
  16. {
  17. Id: 3,
  18. Passport: "user_3",
  19. Password: "",
  20. Nickname: "name_3",
  21. CreateTime: "2018-10-24 10:00:00",
  22. },
  23. {
  24. Id: 4,
  25. Passport: "user_4",
  26. Password: "",
  27. Nickname: "name_4",
  28. CreateTime: "2018-10-24 10:00:00",
  29. },
  30. {
  31. Id: 5,
  32. Passport: "user_5",
  33. Password: "",
  34. Nickname: "name_5",
  35. CreateTime: "2018-10-24 10:00:00",
  36. },
  37. {
  38. Id: 6,
  39. Passport: "user_6",
  40. Password: "",
  41. Nickname: "name_6",
  42. CreateTime: "2018-10-24 10:00:00",
  43. },
  44. {
  45. Id: 7,
  46. Passport: "user_7",
  47. Password: "",
  48. Nickname: "name_7",
  49. CreateTime: "2018-10-24 10:00:00",
  50. },
  51. {
  52. Id: 8,
  53. Passport: "user_8",
  54. Password: "",
  55. Nickname: "name_8",
  56. CreateTime: "2018-10-24 10:00:00",
  57. },
  58. {
  59. Id: 9,
  60. Passport: "user_9",
  61. Password: "",
  62. Nickname: "name_9",
  63. CreateTime: "2018-10-24 10:00:00",
  64. },
  65. {
  66. Id: 10,
  67. Passport: "user_10",
  68. Password: "",
  69. Nickname: "name_10",
  70. CreateTime: "2018-10-24 10:00:00",
  71. },
  72. ]

Type Conversion - Interface - 图3tip

As you can see, the custom UnmarshalValue type conversion method does not use reflection, which greatly improves conversion performance. You can try increasing the data volume (e.g., 1 million) and compare the time cost of type conversion without UnmarshalValue.

2. Custom Binary TCP Data Unpacking

An example of unpacking a TCP communication data packet.

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/gogf/gf/v2/crypto/gcrc32"
  6. "github.com/gogf/gf/v2/encoding/gbinary"
  7. "github.com/gogf/gf/v2/util/gconv"
  8. )
  9. type Pkg struct {
  10. Length uint16 // Total length.
  11. Crc32 uint32 // CRC32.
  12. Data []byte
  13. }
  14. // NewPkg creates and returns a package with given data.
  15. func NewPkg(data []byte) *Pkg {
  16. return &Pkg{
  17. Length: uint16(len(data) + 6),
  18. Crc32: gcrc32.Encrypt(data),
  19. Data: data,
  20. }
  21. }
  22. // Marshal encodes the protocol struct to bytes.
  23. func (p *Pkg) Marshal() []byte {
  24. b := make([]byte, 6+len(p.Data))
  25. copy(b, gbinary.EncodeUint16(p.Length))
  26. copy(b[2:], gbinary.EncodeUint32(p.Crc32))
  27. copy(b[6:], p.Data)
  28. return b
  29. }
  30. // UnmarshalValue decodes bytes to protocol struct.
  31. func (p *Pkg) UnmarshalValue(v interface{}) error {
  32. b := gconv.Bytes(v)
  33. if len(b) < 6 {
  34. return errors.New("invalid package length")
  35. }
  36. p.Length = gbinary.DecodeToUint16(b[:2])
  37. if len(b) < int(p.Length) {
  38. return errors.New("invalid data length")
  39. }
  40. p.Crc32 = gbinary.DecodeToUint32(b[2:6])
  41. p.Data = b[6:]
  42. if gcrc32.Encrypt(p.Data) != p.Crc32 {
  43. return errors.New("crc32 validation failed")
  44. }
  45. return nil
  46. }
  47. func main() {
  48. var p1, p2 *Pkg
  49. // Create a demo pkg as p1.
  50. p1 = NewPkg([]byte("123"))
  51. fmt.Println(p1)
  52. // Convert bytes from p1 to p2 using gconv.Struct.
  53. err := gconv.Struct(p1.Marshal(), &p2)
  54. if err != nil {
  55. panic(err)
  56. }
  57. fmt.Println(p2)
  58. }

After execution, the terminal output:

  1. &{9 2286445522 [49 50 51]}
  2. &{9 2286445522 [49 50 51]}