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

执行后,输出结果为:

{
	"Name": "john",
	"Uid": 1
}

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

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

{
	"Base": {
		"id": 1,
		"uid": 100,
		"create_time": "2019"
	},
	"nickname": "John",
	"passport": "john",
	"password": "123456"
}

示例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

4 Comments

  1. 在下面所示的代码中, jsonrpcBase 是不会被转换的!

    type jsonrpcBase struct {
    Jsonrpc string `json:"jsonrpc"`
    Id int `json:"id"`
    }

    type jsonrpcReq struct {
    jsonrpcBase
    Method string `json:"method"`
    Params interface{} `json:"params"`
    }

    1. 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",
      }

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

    1. 既然都知道转换定义了 那为何不直接自己实现这个转换方法呢.通过注册的方式去实现这个转换反而多此一举.