前面关于复杂类型的转换功能如果大家觉得还不够的话,那么您可以了解下Scan转换方法,该方法可以实现对任意参数到struct/struct数组/map/map数组的转换,并且根据开发者输入的转换目标参数自动识别执行转换。

该方法定义如下:

// Scan automatically calls MapToMap, MapToMaps, Struct or Structs function according to
// the type of parameter `pointer` to implement the converting.
// It calls function MapToMap if `pointer` is type of *map to do the converting.
// It calls function MapToMaps if `pointer` is type of *[]map/*[]*map to do the converting.
// It calls function Struct if `pointer` is type of *struct/**struct to do the converting.
// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting.
func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error)

我们接下来看几个示例便可快速理解。

自动识别转换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.Scan(params, &user); err != nil {
		panic(err)
	}
	g.Dump(user)
}

执行后,输出结果为:

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

自动识别转换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.Slice{
		g.Map{
			"uid":  1,
			"name": "john",
		},
		g.Map{
			"uid":  2,
			"name": "smith",
		},
	}
	var users []*User
	if err := gconv.Scan(params, &users); err != nil {
		panic(err)
	}
	g.Dump(users)
}

执行后,终端输出:

[
    {
        Uid:  1,
        Name: "john",
    },
    {
        Uid:  2,
        Name: "smith",
    },
]

自动识别转换Map

package main

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

func main() {
	var (
		user   map[string]string
		params = g.Map{
			"uid":  1,
			"name": "john",
		}
	)
	if err := gconv.Scan(params, &user); err != nil {
		panic(err)
	}
	g.Dump(user)
}

执行后,输出结果为:

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

自动识别转换Map数组

package main

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

func main() {
	var (
		users  []map[string]string
		params = g.Slice{
			g.Map{
				"uid":  1,
				"name": "john",
			},
			g.Map{
				"uid":  2,
				"name": "smith",
			},
		}
	)
	if err := gconv.Scan(params, &users); err != nil {
		panic(err)
	}
	g.Dump(users)
}

执行后,输出结果为:

[
    {
        "uid":  "1",
        "name": "john",
    },
    {
        "uid":  "2",
        "name": "smith",
    },
]
















Content Menu

  • No labels

8 Comments

  1. 这个不能做pbentity与entity的转换。会报错:

    value of type *gtime.Time cannot be converted to type timestamppb.Timestamp

    要是能把这个转换给兼容下就方便了。或者,有没有可以加个参数,自定义转换函数?可以自己可选地转换某些类型。
    虽然有UnmarshalValue,但是,针对三方包的结构没办法啊。。。。
  2. 郭强 我在 gconv_struct.go中加入了以下代码(顺手写的,将就看吧,就这个意思):

    var converters map[reflect.Type]map[reflect.Type]reflect.Value
    
    
    func init() {
        converters = make(map[reflect.Type]map[reflect.Type]reflect.Value)
    }
    
    func RegisterConverter(fn interface{}) {
        v := reflect.ValueOf(fn)
        t := v.Type()
        if t.Kind() != reflect.Func {
            panic("RegisterConverter must pass func into it")
        }
        n := t.NumIn()
        if n != 1 {
            panic(fmt.Sprintf("%v must be 1 params", t.Name()))
        }
        at := t.In(0)
        bt := t.Out(0)
    
        m1, ok := converters[at]
        if !ok {
            m1 = map[reflect.Type]reflect.Value{}
            converters[at] = m1
        }
        m1[bt] = reflect.ValueOf(fn)
    }
    
    func CallConverter(a reflect.Value, b reflect.Value) bool {
        at := a.Type()
        bt := b.Type()
    
        m1, ok := converters[at]
        if ok {
            fn, ok := m1[bt]
            if ok {
                ret := fn.Call([]reflect.Value{a})
                b.Elem().Set(ret[0].Elem())
                return true
            }
        }
        return false
    }

    然后在UnmarshalValue调用下方加入了:

    if CallConverter(reflect.ValueOf(value), reflect.ValueOf(pointer)) {
        return nil, true
    }

    这样的话,就可以进行如下的转换了,这样对于一些第三方的类型,也可以很方便地转换了,希望官方能考虑将类似的功能加入GoFrame中:

    gconv.RegisterConverter(func(a *gtime.Time) *timestamppb.Timestamp {
        return utility.GTimeToPbTime(a)
    })
    gconv.RegisterConverter(func(a *timestamppb.Timestamp) *gtime.Time {
        return utility.PbTimeToGTime(a)
    })
    
    
    
    var gg *timestamppb.Timestamp
    var n *gtime.Time = gtime.Now()
    var m *gtime.Time
    
    err := gconv.Scan(n, &gg)
    t.AssertNil(err)
    
    err = gconv.Scan(gg, &m)
    t.AssertNil(err)
    
    fmt.Printf("n: %v gg:%v m:%v", n, gg, m)
    1. 这个思路很不错,我个人觉得很赞!👍👍 可以把这个特性提个pr,有些细节我们一起来完善。

      1. 之前的写法有些问题,后面又自己又调整了下,才OK。之前把转换调用放在了bindVarToReflectValueWithInterfaceCheck中,这个会导致在复杂结构中pointer为nil时得不到转换,,从而进入到doConvertWithReflectValueSet中,使得最后的gtime.Time转换成了0值。现在放到调用bindVarToReflectValueWithInterfaceCheck之前了,几个简单的测试用例看着是正常了。等我再多优化优化~ ^O^

        1. 这个特性不错的,你加我微信呢,我怕后面找不到你了。我的微信是 389961817

          1. 以前加过。。。还在群里。。。

      2. 发起了个PR,反射我用得不多,帮看看有没有需要改进的地方,特别是性能相关的。

  3. Scan()相较于Map() Struct() Structs() 是不是更底层的实现?

    也就是说Struct()能做到的Scan()也能做到,但是反过来则不行?