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