- Created by 郭强, last modified by 张金富 on May 19, 2023
项目中我们经常会遇到大量struct
的使用,以及各种数据类型到struct
的转换/赋值(特别是json
/xml
/各种协议编码转换)。为提高编码及项目维护效率,gconv
模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。
gconv
模块通过Struct
转换方法执行struct
类型转换,其定义如下:
// Struct maps the params key-value pairs to the corresponding struct object's attributes. // The third parameter `mapping` is unnecessary, indicating the mapping rules between the // custom key name and the attribute name(case sensitive). // // Note: // 1. The `params` can be any type of map/struct, usually a map. // 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object // or struct pointer. // 3. Only the public attributes of struct object can be mapped. // 4. If `params` is a map, the key of the map `params` can be lowercase. // It will automatically convert the first letter of the key to uppercase // in mapping procedure to do the matching. // It ignores the map key, if it does not match. func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error)
其中:
params
为需要转换到struct
的变量参数,可以为任意数据类型,常见的数据类型为map
。pointer
为需要执行转的目标struct
对象,这个参数必须为该struct
的对象指针,转换成功后该对象的属性将会更新。mapping
为自定义的map键名
到strcut属性
之间的映射关系,此时params
参数必须为map
类型,否则该参数无意义。大部分场景下使用可以不用提供该参数,直接使用默认的转换规则即可。
更多的struct
相关转换方法请参考接口文档:https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv
转换规则
gconv
模块的struct
转换特性非常强大,支持任意数据类型到struct
属性的映射转换。在没有提供自定义mapping
转换规则的情况下,默认的转换规则如下:
struct
中需要匹配的属性必须为 公开属性 (首字母大写)。- 根据
params
类型的不同,逻辑会有不同:params
参数类型为map
:键名会自动按照 不区分大小写 且 忽略特殊字符 的形式与struct属性进行匹配。params
参数为其他类型:将会把该变量值与struct
的第一个属性进行匹配。- 此外,如果
struct
的属性为复杂数据类型如slice
,map
,strcut
那么会进行递归匹配赋值。
- 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。
以下是几个map
键名与struct
属性名称的示例:
map键名 struct属性 是否匹配 name Name match Email Email match nickname NickName match NICKNAME NickName match Nick-Name NickName match nick_name NickName match nick name NickName match NickName Nick_Name match Nick-name Nick_Name match nick_name Nick_Name match nick name Nick_Name match
自动创建对象
当给定的pointer
参数类型为**struct
时,Struct
方法内部将会自动创建该struct
对象,并修改传递变量指向的指针地址。
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) func main() { type User struct { Uid int Name string } params := g.Map{ "uid": 1, "name": "john", } var user *User if err := gconv.Struct(params, &user); err != nil { panic(err) } g.Dump(user) }
执行后,输出结果为:
{ Uid: 1, Name: "john", }
Struct
递归转换
递归转换是指当struct
对象包含子对象时,并且子对象是embedded
方式定义时,可以将params
参数数据(第一个参数)同时递归地映射到其子对象上,常用于带有继承对象的struct
上。
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) func main() { type Ids struct { Id int `json:"id"` Uid int `json:"uid"` } type Base struct { Ids CreateTime string `json:"create_time"` } type User struct { Base Passport string `json:"passport"` Password string `json:"password"` Nickname string `json:"nickname"` } data := g.Map{ "id" : 1, "uid" : 100, "passport" : "john", "password" : "123456", "nickname" : "John", "create_time" : "2019", } user := new(User) gconv.Struct(data, user) g.Dump(user) }
执行后,终端输出结果为:
{ Id: 1, Uid: 100, CreateTime: "2019", Passport: "john", Password: "123456", Nickname: "John", }
示例1,基本使用
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) type User struct { Uid int Name string SiteUrl string NickName string Pass1 string `c:"password1"` Pass2 string `c:"password2"` } func main() { var user *User // 使用默认映射规则绑定属性值到对象 user = new(User) params1 := g.Map{ "uid": 1, "Name": "john", "site_url": "https://goframe.org", "nick_name": "johng", "PASS1": "123", "PASS2": "456", } if err := gconv.Struct(params1, user); err == nil { g.Dump(user) } // 使用struct tag映射绑定属性值到对象 user = new(User) params2 := g.Map{ "uid": 2, "name": "smith", "site-url": "https://goframe.org", "nick name": "johng", "password1": "111", "password2": "222", } if err := gconv.Struct(params2, user); err == nil { g.Dump(user) } }
可以看到,我们可以直接通过Struct
方法将map
按照默认规则绑定到struct
上,也可以使用struct tag
的方式进行灵活的设置。此外,Struct
方法有第三个map
参数,用于指定自定义的参数名称到属性名称的映射关系。
执行后,输出结果为:
{ Uid: 1, Name: "john", SiteUrl: "https://goframe.org", NickName: "johng", Pass1: "123", Pass2: "456", } { Uid: 2, Name: "smith", SiteUrl: "https://goframe.org", NickName: "johng", Pass1: "111", Pass2: "222", }
示例2,复杂属性类型
属性支持struct
对象或者struct
对象指针(目标为指针且未nil
时,转换时会自动初始化)转换。
package main import ( "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/frame/g" "fmt" ) func main() { type Score struct { Name string Result int } type User1 struct { Scores Score } type User2 struct { Scores *Score } user1 := new(User1) user2 := new(User2) scores := g.Map{ "Scores": g.Map{ "Name": "john", "Result": 100, }, } if err := gconv.Struct(scores, user1); err != nil { fmt.Println(err) } else { g.Dump(user1) } if err := gconv.Struct(scores, user2); err != nil { fmt.Println(err) } else { g.Dump(user2) } }
执行后,输出结果为:
{ Scores: { Name: "john", Result: 100, }, } { Scores: { Name: "john", Result: 100, }, }
Content Menu
- No labels
7 Comments
杨率帅
在下面所示的代码中, jsonrpcBase 是不会被转换的!
type jsonrpcBase struct {
Jsonrpc string `json:"jsonrpc"`
Id int `json:"id"`
}
type jsonrpcReq struct {
jsonrpcBase
Method string `json:"method"`
Params interface{} `json:"params"`
}
KingKong
2.0版本确实是存在这个问题,我用的示例中的代码,也没有成功。
result:
{
Id: 1,
Uid: 100,
CreateTime: "2019",
Passport: "john",
Password: "123456",
Nickname: "John",
}
shawn_hou
能否支持Struct类型的整体转换,比如一个字符串string,转换为一个Struct,转换规则自定义的方式,类比java中的各种mapper
例如,我现在有个这样的场景,前端输入一个子网网段地址:172.16.1.0/28,但是在struct中定义的字段为: network net.IPNet
像这种的,我想通过convert函数,把”172.16.1.0/28“作为一个整体的输入,通过转换规则:以 / 做分割,得到IP和 mask,然后构造得到IPNet类型的 network字段。
请问有办法可以实现在convert时指定,或者通过全局注入自定义类型构造方法的方式,来将此种类型的转换需求通用化吗?
糖水不加糖
既然都知道转换定义了 那为何不直接自己实现这个转换方法呢.通过注册的方式去实现这个转换反而多此一举.
刘三少
郭强 期待一个加强功能,A→B转换时,通过mapping下标加点的办法,使B里面的字段b能匹配到b某结构体字段里面的值b.bb
就是在转换时,允许提升字段值的层级
lfzxs
在做grpc时发现个问题,当数据库返回的结构体包含*gtime.Time类型值时,转换会失败。
超人