添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

项目中我们经常会遇到大量 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)

其中:

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

更多的 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 属性名称的示例:

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 {
		CreateTime string `json:"create_time"`
	type User struct {
		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,

2.0版本确实是存在这个问题,我用的示例中的代码,也没有成功。

type Ids struct {
Id int `json:"id"`
Uid int `json:"uid"`
}
type Base struct {
Ids
CreateTime string `json:"create_time"`
}
type Users struct {
Base
Passport string `json:"passport"`
Password string `json:"password"`
Nickname string `json:"nickname"`
}
datas := g.Map{
"id" : 1 ,
"uid" : 100 ,
"passport" : "john" ,
"password" : "123456" ,
"nickname" : "John" ,
"create_time" : "2019" ,
}
users := new (Users)
gconv. Struct (datas, users)
g. Dump (users)

result:

{
Id:         1,
Uid:        100,
CreateTime: "2019",
Passport:   "john",
Password:   "123456",
Nickname:   "John",
}

  • Jun 23, 2022
  • 能否支持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时指定,或者通过全局注入自定义类型构造方法的方式,来将此种类型的转换需求通用化吗?

  • Nov 29, 2022
  • 在做grpc时发现个问题,当数据库返回的结构体包含*gtime.Time类型值时,转换会失败。

    type SysDictListRes struct{    
        RecordId  int64   `json:"recordId"`    
        RecordStatus  int   `json:"recordStatus"`    
        CreatedBy  string   `json:"createdBy"`    
        CreatedAt  *gtime.Time   `json:"createdAt"`    
        DictCode  string   `json:"dictCode"`    
        DictName  string   `json:"dictName"`    
        Remark  string   `json:"remark"`    
    type SysDict struct {
    	state         protoimpl.MessageState
    	sizeCache     protoimpl.SizeCache
    	unknownFields protoimpl.UnknownFields
    	RecordId     int64                  `protobuf:"varint,1,opt,name=RecordId,proto3" json:"RecordId" dc:"编号 记录ID"`                // 编号 记录ID
    	RecordStatus int32                  `protobuf:"varint,2,opt,name=RecordStatus,proto3" json:"RecordStatus" dc:"记录状态, 0=启用,1禁用"` // 记录状态, 0=启用,1禁用
    	CreatedBy    string                 `protobuf:"bytes,3,opt,name=CreatedBy,proto3" json:"CreatedBy" dc:"创建人编号"`                 // 创建人编号
    	CreatedAt    *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty" dc:"创建时间 默认为当前时间"`          // 创建时间 默认为当前时间
    	DictCode     string                 `protobuf:"bytes,8,opt,name=DictCode,proto3" json:"dictCode" dc:"字典编码 用户自定义,同一租户下唯一"`      // 字典编码 用户自定义,同一租户下唯一
    	DictName     string                 `protobuf:"bytes,9,opt,name=DictName,proto3" json:"DictName" dc:"字典名称"`                    // 字典名称
    	Remark       string                 `protobuf:"bytes,10,opt,name=Remark,proto3" json:"Remark,omitempty" dc:"备注"`                         // 备注
    func main() {
    	source := SysDictListRes{
    		RecordId:     1665646348394827776,
    		RecordStatus: 0,
    		CreatedAt:   gtime.New(time.Now()),
    		CreatedBy: "tester",
    		DictCode:  "sys_test1",
    		DictName:  "测试字典1",
           dist := new(SysDict)
           gconv.Struct(source, dist)
           fmt.Println(source)
           fmt.Println(dist)
                    


    type GoodsDetailRes struct {
    //model.GoodsItem

    Id uint `json:"id" description:""` //
    Title string `json:"title" description:"标题"` // 标题
    Images []string `json:"images" description:"图片"` // 图片
    CateId int `json:"cateId" description:"分类ID"` // 分类ID
    Price float64 `json:"price" description:"价格"` // 价格
    Limit int `json:"limit" description:"限购"` // 限购
    Content string `json:"content" description:"详情"` // 详情
    Status int `json:"status" description:"状态"` // 状态
    Picurl string `json:"picurl" description:""`
    Sold int `json:"sold" description:"已售"` // 已售
    Skus []model.GoodsSku `json:"skus" description:"SKU"` // SKU
    Attr g.Map `json:"attr" description:"属性"` // 属性
    }

    type GoodsSku struct {
    Hide int `json:"hide" description:""`
    Name string `json:"name" description:""`
    Picurl string `json:"picurl" description:""`
    Price float64 `json:"price" description:""`
    Sid uint `json:"sid" description:""`
    Sold int `json:"sold" description:""`
    Sum uint `json:"sum" description:""`
    VipPrice float64 `json:"vip_price" description:""`
    }

    这一行,引用了一个struct的话
    Skus []model.GoodsSku `json:"skus" description:"SKU"` // SKU
    所有浮点数,以及整数, 全转换成了0呢.
    如果换成
    Skus []g.Map `json:"skus" description:"SKU"` // SKU
    就没事
    [{
    "hide"0,
    "name""金黑面钢带款",
    "picurl""http://mt-shop.app.com/uploads/images/2023/06/05/2412529ba9f24152e8b61153bbfd87fb.jpg",
    "price""5",
    "sid""27244498",
    "sold""3",
    "sum""10",
    "vip_price""4"},{
    "hide"0,
    "name""黑面钢带款",
    "picurl""http://mt-shop.app.com/uploads/images/2023/06/05/234439d350b9d1cc8d9392b1aad66681.jpg",
    "price""1",
    "sid""61671448",
    "sold""5",
    "sum""15",
    "vip_price""0.5"},{
    "hide"0,
    "name""白面钢带款",
    "picurl""http://mt-shop.app.com/uploads/images/2023/06/05/e168d5daa9eeb6b3b9fbb8d20c7341e1.jpg",
    "price""2",
    "sid""54686784",
    "sold""2",
    "sum""20",
    "vip_price""1"}]
  • Jun 21, 2023
  •