项目中我们经常会遇到大量 struct 的使用,以及各种数据类型到 struct 的转换/赋值(特别是 json/ xml/各种协议编码转换)。为提高编码及项目维护效率, gconv 模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。

gconv 模块通过 Struct 转换方法执行 struct 类型转换,其定义如下:

  1. // Struct maps the params key-value pairs to the corresponding struct object's attributes.
  2. // The third parameter `mapping` is unnecessary, indicating the mapping rules between the
  3. // custom key name and the attribute name(case sensitive).
  4. //
  5. // Note:
  6. // 1. The `params` can be any type of map/struct, usually a map.
  7. // 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object
  8. // or struct pointer.
  9. // 3. Only the public attributes of struct object can be mapped.
  10. // 4. If `params` is a map, the key of the map `params` can be lowercase.
  11. // It will automatically convert the first letter of the key to uppercase
  12. // in mapping procedure to do the matching.
  13. // It ignores the map key, if it does not match.
  14. func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error)

其中:

  1. params 为需要转换到 struct 的变量参数,可以为任意数据类型,常见的数据类型为 map
  2. pointer 为需要执行转的目标 struct 对象,这个参数必须为该 struct 的对象指针,转换成功后该对象的属性将会更新。
  3. mapping 为自定义的 map键名strcut属性 之间的映射关系,此时 params 参数必须为 map 类型,否则该参数无意义。大部分场景下使用可以不用提供该参数,直接使用默认的转换规则即可。

类型转换-Struct转换 - 图1提示

更多的 struct 相关转换方法请参考接口文档: https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv

转换规则

gconv 模块的 struct 转换特性非常强大,支持任意数据类型到 struct 属性的映射转换。在没有提供自定义 mapping 转换规则的情况下,默认的转换规则如下:

  1. struct 中需要匹配的属性必须为 公开属性(首字母大写)。
  2. 根据 params 类型的不同,逻辑会有不同:
    • params 参数类型为 map:键名会自动按照 不区分大小写忽略特殊字符 的形式与struct属性进行匹配。
    • params 参数为其他类型:将会把该变量值与 struct 的第一个属性进行匹配。
    • 此外,如果 struct 的属性为复杂数据类型如 slice, map, strcut 那么会进行递归匹配赋值。
  3. 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。

匹配规则优先级说明(只针对于map到struct的转换)

1.如果 mapping 参数不为空,将会按照 mappingkeystrcut字段名 之间的映射关系。

2.如果设置了字段的 tag,会使用 tag 来匹配 params 参数的 key

  1. 如果没有设置 `tag`gconv将会依次按照 `gconv, param, c, p, json` 这个顺序来查找字段是否有对应的 `tag`

3.按照 字段名 匹配。

4.如果以上都没有匹配到,gconv将会遍历 params 参数所有的 key,按照 以下规则来匹配

字段名:忽略大小写和下划线

key: 忽略大小写和下划线和特殊字符

提示

类型转换-Struct转换 - 图2注意

没有特殊情况,请尽量满足前三条规则,第四条规则性能较差

以下是几个 map 键名与 struct 属性名称的示例:

  1. map键名 struct属性 是否匹配
  2. name Name match
  3. Email Email match
  4. nickname NickName match
  5. NICKNAME NickName match
  6. Nick-Name NickName match
  7. nick_name NickName match
  8. nick name NickName match
  9. NickName Nick_Name match
  10. Nick-name Nick_Name match
  11. nick_name Nick_Name match
  12. nick name Nick_Name match

自动创建对象

当给定的 pointer 参数类型为 **struct 时, Struct 方法内部将会自动创建该 struct 对象,并修改传递变量指向的指针地址。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/util/gconv"
  5. )
  6. func main() {
  7. type User struct {
  8. Uid int
  9. Name string
  10. }
  11. params := g.Map{
  12. "uid": 1,
  13. "name": "john",
  14. }
  15. var user *User
  16. if err := gconv.Struct(params, &user); err != nil {
  17. panic(err)
  18. }
  19. g.Dump(user)
  20. }

执行后,输出结果为:

  1. {
  2. Uid: 1,
  3. Name: "john",
  4. }

Struct 递归转换

递归转换是指当 struct 对象包含子对象时,并且子对象是 embedded 方式定义时,可以将 params 参数数据(第一个参数)同时递归地映射到其子对象上,常用于带有继承对象的 struct 上。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/util/gconv"
  5. )
  6. func main() {
  7. type Ids struct {
  8. Id int `json:"id"`
  9. Uid int `json:"uid"`
  10. }
  11. type Base struct {
  12. Ids
  13. CreateTime string `json:"create_time"`
  14. }
  15. type User struct {
  16. Base
  17. Passport string `json:"passport"`
  18. Password string `json:"password"`
  19. Nickname string `json:"nickname"`
  20. }
  21. data := g.Map{
  22. "id" : 1,
  23. "uid" : 100,
  24. "passport" : "john",
  25. "password" : "123456",
  26. "nickname" : "John",
  27. "create_time" : "2019",
  28. }
  29. user := new(User)
  30. gconv.Struct(data, user)
  31. g.Dump(user)
  32. }

执行后,终端输出结果为:

  1. {
  2. Id: 1,
  3. Uid: 100,
  4. CreateTime: "2019",
  5. Passport: "john",
  6. Password: "123456",
  7. Nickname: "John",
  8. }

示例1,基本使用

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/util/gconv"
  5. )
  6. type User struct {
  7. Uid int
  8. Name string
  9. SiteUrl string
  10. NickName string
  11. Pass1 string `c:"password1"`
  12. Pass2 string `c:"password2"`
  13. }
  14. func main() {
  15. var user *User
  16. // 使用默认映射规则绑定属性值到对象
  17. user = new(User)
  18. params1 := g.Map{
  19. "uid": 1,
  20. "Name": "john",
  21. "site_url": "https://goframe.org",
  22. "nick_name": "johng",
  23. "PASS1": "123",
  24. "PASS2": "456",
  25. }
  26. if err := gconv.Struct(params1, user); err == nil {
  27. g.Dump(user)
  28. }
  29. // 使用struct tag映射绑定属性值到对象
  30. user = new(User)
  31. params2 := g.Map{
  32. "uid": 2,
  33. "name": "smith",
  34. "site-url": "https://goframe.org",
  35. "nick name": "johng",
  36. "password1": "111",
  37. "password2": "222",
  38. }
  39. if err := gconv.Struct(params2, user); err == nil {
  40. g.Dump(user)
  41. }
  42. }

可以看到,我们可以直接通过 Struct 方法将 map 按照默认规则绑定到 struct 上,也可以使用 struct tag 的方式进行灵活的设置。此外, Struct 方法有第三个 map 参数,用于指定自定义的参数名称到属性名称的映射关系。

执行后,输出结果为:

  1. {
  2. Uid: 1,
  3. Name: "john",
  4. SiteUrl: "https://goframe.org",
  5. NickName: "johng",
  6. Pass1: "123",
  7. Pass2: "456",
  8. }
  9. {
  10. Uid: 2,
  11. Name: "smith",
  12. SiteUrl: "https://goframe.org",
  13. NickName: "johng",
  14. Pass1: "111",
  15. Pass2: "222",
  16. }

示例2,复杂属性类型

属性支持 struct 对象或者 struct 对象指针(目标为指针且为 nil 时,转换时会自动初始化)转换。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/util/gconv"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "fmt"
  6. )
  7. func main() {
  8. type Score struct {
  9. Name string
  10. Result int
  11. }
  12. type User1 struct {
  13. Scores Score
  14. }
  15. type User2 struct {
  16. Scores *Score
  17. }
  18. user1 := new(User1)
  19. user2 := new(User2)
  20. scores := g.Map{
  21. "Scores": g.Map{
  22. "Name": "john",
  23. "Result": 100,
  24. },
  25. }
  26. if err := gconv.Struct(scores, user1); err != nil {
  27. fmt.Println(err)
  28. } else {
  29. g.Dump(user1)
  30. }
  31. if err := gconv.Struct(scores, user2); err != nil {
  32. fmt.Println(err)
  33. } else {
  34. g.Dump(user2)
  35. }
  36. }

执行后,输出结果为:

  1. {
  2. Scores: {
  3. Name: "john",
  4. Result: 100,
  5. },
  6. }
  7. {
  8. Scores: {
  9. Name: "john",
  10. Result: 100,
  11. },
  12. }