Go源码分析:mapstructure

在这里插入图片描述

1、工程包与文档

官方文档:https://godoc.org/github.com/mitchellh/mapstructure#DecodeHookFunc

github地址:https://github.com/goinggo/mapstructure

提供Hook地址:https://github.com/mitchellh/mapstructure/blob/master/decode_hooks.go

官方工程包获取方式: go get github.com/goinggo/mapstructure

获取默认hook工程包方式:go get github.com/mitchellh/mapstructure

2、配置说明

    在decoder进行decode调用之前,需要设置一下decoder的配置信息,这边的配置信息都放在DecoderConfig中,具体来看一下对应的字段

// DecoderConfig is the configuration that is used to create a new decoder
// and allows customization of various aspects of decoding.
type DecoderConfig struct {
   

	DecodeHook DecodeHookFunc

	ErrorUnused bool

	WeaklyTypedInput bool

	Metadata *Metadata

	Result interface{
   }

	TagName string
}

a.DecodeHook

    DecodeHook是一个hook方法,在调用decode进行字段的转换之前,会先调用hook方法,先对被转换的数据进行处理,然后再对被处理过的数据进行转换

    前提是hook存在,DecodeHook对应的是 DecodeHookFunc方法:

type DecodeHookFunc func(reflect.Kind, reflect.Kind, interface{
   }) (interface{
   }, error)

    这边的参数解释可以查看官网的说明,并且官网在 decode_hook 提供了一部分转换的hook方法,可以参考

    hook的具体作用可以举例说明:先将字符串对应的字段转换成时间类型,再调用转换方法.
    

b.ErrorUnused

    ErrorUnused默认为false,在需要的情况下可以将该字段设置为true。当该字段为true,如果map转换过程中,有任意一个key的值没办法转换则会产生error数据返回

    

c.WeaklyTypedInput

    WeaklyTypedInput默认是false,该标识主要是控制弱类型转换处理,具体的转换规则如下,在decode区分不同类型转换的时候会具体处理:

  • bools to string (true = “1”, false = “0”)
  • numbers to string (base10)
  • bools to int/uint (true = 1, false = 0)
  • strings to int/uint(base implied by prefix)
  • int to bool (true if value != 0)
  • string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, FALSE,false, False. Anything else is an error)
  • empty array = empty map and vice versa

d.Metadata

    MetaData的结构如下:

// Metadata contains information about decoding a structure that
// is tedious or difficult to get otherwise.
type Metadata struct {
   
	// Keys are the keys of the structure which were successfully decoded
	Keys []string

	// Unused is a slice of keys that were found in the raw value but
	// weren't decoded since there was no matching field in the result interface
	Unused []string
}

    Keys存储转换过程中被转换成功的key数组,Unused存储转换过程中没有被转换的key数组,这边的测试表明如果要让metadata生效,必须设置通过Decode处理,DecodePath似乎没办法处理.
    

e.Result

    Result是Decode转换的结果,需要注意的是,这边传入的必须是ptr类型,比如说 Obj是结构体,这边需要传入的是 &Obj,如果不是 &Obj 则配置decode没办法成功。

    这边在配置的时候没有直接报错,而是通过返回error的方式有点奇怪,使用者没办法一下子看出来问题。
    

f.TagName

    TagName默认会处理成 mapstructure,这个是标注在结构体上的tag标识,通过TagName去获取tag上的字段配置,该字段可设置。
    

3、Decode:结构体精确转换

a.零值处理

    这边主要是处理两种零值的情况,nil和默认值。

    如果被转换的数据是nil,则直接返回nil,如果被转换的数据是该类型的零值,则直接设置Result为零值返回。

    具体看一下代码:

	//read note 结构体为空直接返回
	if data == nil {
   
		// If the data is nil, then we don't set anything.
		return nil
	}

	dataVal := reflect.ValueOf(data)
	//read note 非IsValid的对象,设置零值
	if !dataVal.IsValid() {
   
		// If the data value is invalid, then we just set the value
		// to be the zero value.
		val.Set(reflect.Zero(val.Type()))
		return nil
	}

b.Hook调用

    在真正的转换之前,需要判断hookFunc是否为空,不为空的话需要调用hookFunc对被转换的数据进行处理,然后返回被处理后的数据。

    这个被处理后的数据才是下面真正进行转换处理的数据,具体看一下代码:

	//read note hook的调用,调用的结果data会用在下面的判断中
	if d.config.DecodeHook != nil {
   
		// We have a DecodeHook, so let's pre-process the data.
		var err error
		data, err = d.config.DecodeHook(d.getKind(dataVal), d.getKind(val), data)
		if err != nil {
   
			return err
		}
	}

c.根据类型的decode处理

    获得转换结果的数据类型,根据数据类型进行不同的处理,这边分成了10个不同的处理,分别看一下:

1.bool

    decodeBool会根据WeakluTypeInput是否开启进行转换或者是构建错误信息,比较简单,具体看代码:

func (d *Decoder) decodeBool(name string, data interface{
   }, val reflect.Value) error {
   
	dataVal := reflect.ValueOf(data)
	dataKind := d.getKind(dataVal)

	//read note 这边的转换规则如下:
	//	1、bool类型直接转换
	//	开启了弱类型转换标识的
	//	2、数值类型判断是否为0,0是false
	//	3、string类型,先进行bool转换,如果不能转换,空字符表示false,否则错误
	//	4、其他类型错误
	switch {
   
	case dataKind == reflect.Bool:
		val.SetBool(dataVal.Bool())
	case dataKind == reflect.Int && d.config.WeaklyTypedInput:
		val.SetBool(dataVal.Int() != 0)
	case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
		val.SetBool(dataVal.Uint() != 0)
	case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
		val.SetBool(dataVal.Float() != 0)
	case dataKind == reflect.String && d.config.WeaklyTypedInput:
		b, err := strconv.ParseBool(dataVal.String())
		if err == nil {
   
			val.SetBool(b)
		} else if dataVal.String() == "" {
   
			val.SetBool(false)
		} else {
   
			return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
		}
	default:
		return fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type())
	}

	return nil
}

2.Interface

    decodeBasic会判断被转换对象是否可以直接转换成interface,如果可以转换,则进行转换,不能则构建错误信息:

// This decodes a basic type (bool, int, string, etc.) and sets the
// value to "data" of that type.
func (d *Decoder) decodeBasic(name string, data interface{
   }, val reflect.Value) error {
   
	//read note 如果被转换的结果是interface,则只判断是否 AssignableTo
	dataVal := reflect.ValueOf(data)
	dataValType := dataVal.Type()
	if !dataValType.AssignableTo(val.Type()) {
   
		return fmt.Errorf(
			"'%s' expected type '%s', got '%s'",
			name, val.Type(), dataValType)
	}

	val.Set(dataVal)
	return nil
}

3.string

    decodeString和decodeBool有点类似,具体看代码:

func (d *Decoder) decodeString(name string, data interface{
   }, val reflect.Value) error {
   
	dataVal := reflect.ValueOf(data)
	dataKind := d.getKind(dataVal)
	
	//read note string这边的转换规则如下:
	//	1、如果被转换的就是string,则直接转换成string
	//	开启了弱类型转换标识的
	//	2、bool转换从1或0
	//	3、数值类型按照十进制转换成字符串
	//	4、float类型,按照64位转换
	switch {
   
	case dataKind == reflect.String:
		val.SetString(dataVal.String())
	case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
		if dataVal.Bool() {
   
			val.SetString("1")
		} else {
   
			val.SetString("0")
		}
	case dataKind == reflect.Int && d.config.WeaklyTypedInput:
		val.SetString(strconv.FormatInt(dataVal.Int(), 10))
	case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
		val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
	case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
		val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
	default:
		return fmt.Errorf(
			"'%s' expected type '%s', got unconvertible type '%s'",
			name, val.Type(), dataVal.Type())
	}

	return nil
}

4.int

    decodeInt和decedeBool有点类似,具体看代码:

func (d *Decoder) decodeInt(name string, data interface{
   }, val reflect.Value) error {
   
	dataVal := reflect.ValueOf(data)
	dataKind := d.getKind(dataVal)

	//read note int这边的转换大致如下
	//	1、数值类型,直接转换成int,不考虑精度
	//	如果开启了弱类型转换标识
	//	2、bool类型按照0,1转换
	//	3、字符串通过 ParseInt转换
	//  4、否则错误
	switch {
   
	case dataKind == reflect.Int:
		val.SetInt(dataVal.Int())
	case dataKind == reflect.Uint:
		val.SetInt(int64(dataVal.Uint()))
	case dataKind == reflect.Float32:
		val.SetInt(int64(dataVal.Float()))
	case dataKind == reflect.Bool && d.config.WeaklyTypedInput
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了-凡

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值