gconv.Map支持将任意的mapstruct/*struct类型转换为常用的 map[string]interface{} 类型。当转换参数为struct/*struct类型时,支持自动识别structc/gconv/json 标签,并且可以通过Map方法的第二个参数tags指定自定义的转换标签,以及多个标签解析的优先级。如果转换失败,返回nil

属性标签:当转换struct/*struct类型时,支持 c/gconv/json 属性标签,也支持 -omitempty 标签属性。当使用 - 标签属性时,表示该属性不执行转换;当使用 omitempty 标签属性时,表示当属性为空时(空指针nil, 数字0, 字符串"", 空数组[]等)不执行转换。具体请查看随后示例。

常用转换方法:

func Map(value interface{}, tags ...string) map[string]interface{}
func MapDeep(value interface{}, tags ...string) map[string]interface{}

其中,MapDeep支持递归转换,即会递归转换属性中的struct/*struct对象。

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

基本示例

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
)

func main() {
	type User struct {
		Uid  int    `c:"uid"`
		Name string `c:"name"`
	}
	// 对象
	g.Dump(gconv.Map(User{
		Uid:  1,
		Name: "john",
	}))
	// 对象指针
	g.Dump(gconv.Map(&User{
		Uid:  1,
		Name: "john",
	}))

	// 任意map类型
	g.Dump(gconv.Map(map[int]int{
		100: 10000,
	}))
}

执行后,终端输出:

{
    "name": "john",
    "uid": 1,
}

{
    "name": "john",
    "uid": 1,
}

{
    "100": 10000,
}

属性标签

我们可以通过c/gconv/json 标签来自定义转换后的map键名,当多个标签存在时,按照gconv/c/json的标签顺序进行优先级识别。

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 `c:"-"`
		NickName string `c:"nickname, omitempty"`
		Pass1    string `c:"password1"`
		Pass2    string `c:"password2"`
	}
	user := User{
		Uid:   100,
		Name:  "john",
		Pass1: "123",
		Pass2: "456",
	}
	g.Dump(gconv.Map(user))
}

执行后,终端输出:

{
    "Uid": 100,
    "password1": "123",
    "password2": "456",
    "nickname": "",
}

自定义标签

此外,我们也可以给struct的属性自定义自己的标签名称,并在map转换时通过第二个参数指定标签优先级。

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
)

func main() {
	type User struct {
		Id   int    `c:"uid"`
		Name string `my-tag:"nick-name" c:"name"`
	}
	user := &User{
		Id:   1,
		Name: "john",
	}
	g.Dump(gconv.Map(user, "my-tag"))
}

执行后,输出结果为:

{
	"nick-name": "john",
	"uid": 1,
}

递归转换

当参数为map/struct/*struct类型时,如果键值/属性为一个对象(或者对象指针)时,并且不是embedded结构体且没有任何的别名标签绑定,Map方法将会将对象转换为结果的一个键值。我们可以使用MapDeep方法递归转换参数的子对象,即把属性也转换为map类型。我们来看个例子。

package main

import (
	"fmt"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"reflect"
)

func main() {
	type Base struct {
		Id   int    `c:"id"`
		Date string `c:"date"`
	}
	type User struct {
		UserBase Base   `c:"base"`
		Passport string `c:"passport"`
		Password string `c:"password"`
		Nickname string `c:"nickname"`
	}
	user := &User{
		UserBase: Base{
			Id:   1,
			Date: "2019-10-01",
		},
		Passport: "john",
		Password: "123456",
		Nickname: "JohnGuo",
	}
	m1 := gconv.Map(user)
	m2 := gconv.MapDeep(user)
	g.Dump(m1, m2)
	fmt.Println(reflect.TypeOf(m1["base"]))
	fmt.Println(reflect.TypeOf(m2["base"]))
}

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

{
    "base":     {
        Id:   1,
        Date: "2019-10-01",
    },
    "passport": "john",
    "password": "123456",
    "nickname": "JohnGuo",
}
{
    "base":     {
        "id":   1,
        "date": "2019-10-01",
    },
    "passport": "john",
    "password": "123456",
    "nickname": "JohnGuo",
}
main.Base
map[string]interface {}

看出来差别了吗?









Content Menu

  • No labels

3 Comments

  1. https://github.com/gogf/gf/issues/1209   测试的一个gconv的一个bug。一直没有回复issues  不知是否修复

    1. 感谢反馈,已回复

  2. 有个问题,我如何手动定义某个字段的转换的方法呢?我在数据库ORM操作里,会把自定义的model直接传入Data(),它会调用gconv.Map来转为map类型。

    但是转换的结构不符合我的预期。

    举个例子,struct里含有一个time.Duration的字段,值为1分钟,经过Map转换以后,它变成了"1m0s",但是我想把它转为 60000000000。我看了一下UnmarshalValue,它是从其它类型转为结构的时候用的,没有对应的MarshalValue吗?

    目前我想到几个办法,

    一、这个办法比较粗暴,转为map以后,自己再手动赋值一下(目前我暂时就是用的这个办法),但总有点”打补丁“的感觉:

    data:=gconv.Map(originData)

    data["Duration"] = originData.Duration.Nanoseconds() 

    dao.Datas.Ctx(p.ctx).Data(data).Insert()

    二、通过查看goframe的源码得知,insert之前会判断字段值是否实现driver.Valuer接口,所以自己定义了一个time.Duration的别名,实现了driver.Valuer接口:

    type Duration time.Duration

    func (p Duration) Value() (driver.Value, error) {
        return time.Duration(p).Nanoseconds(), nil
    }

    然后把结构中的time.Duration的字段换成我自己定义的Duration。

    但是这样做也有问题,项目里已经有很多time.Duration的字段了,虽然可以用批量替换代码的方法。

    但是很多第三方包用的就是time.Duration,导致出现大量类似time.Duration(a)和my.Duration(b)这种转换,反而变混乱了。

    三、通过查看goframe的源码得知,可以实现iMapStrAny接口来自己自定义struct到map[string]interface{}的方法 ,然而这种办法得手动处理所有字段,也很麻烦,而且丢弃了tag处理(除非我把doMapConvertForMapOrStructValue里关于tag的处理的部分复制过来)


    不知道有没有什么好的办法。