注:该文章源码分析参考 copier
目录
源码包
包获取路径: go get github.com/jinzhu/copier
代码阅读
1、tag标签说明
must:被must标识的字段必须被复制赋值,如果没有的话,则依据下一个标签进行对应的处理
nopanic:如果被must标识的字段没有复制赋值,声明nopanic会返回error代替panic报错
- : 忽略字段的复制赋值
2、静态参数
这边使用了四个静态参数,如下:
const (
// Denotes that a destination field must be copied to. If copying fails then a panic will ensue.
tagMust uint8 = 1 << iota
// Denotes that the program should not panic when the must flag is on and
// value is not copied. The program will return an error instead.
tagNoPanic
// Ignore a destation field from being copied to.
tagIgnore
// Denotes that the value as been copied
hasCopied
)
作用分别是:
tagMust:结构体中标注了“must”标签的字段,必须被复制值,否则视为error
tagNopanic:和tagMust配套使用,如果设置标签“nopanic”,则如果不满足must的条件,不直接报错,而是返回error代替
tagIgnore:标签为“-”,设置该标签的字段直接忽略复制
hasCopied:这个不是标签标识,而是字段复制的标识,在结构体复制结束之后,设置flag为已经复制.依据这个字段来判断是否复制成功
3、整体设计
这边先说明整个代码的设计思路,然后读者可以依据该思路去查看Copy主方法说明,能够更清晰的理解对应的代码.
大致的设计思路如下:
不可寻址和Invalid的数据直接报错或者返回
判断两个数据结构是不是map,进行map的处理
数组与结构体的处理,按照类型进行数据遍历
循环所有字段,解析tag
判断是否忽略不复制,不复制则跳过
根据字段名进行赋值
根据方法名(同名)进行赋值
赋值成功之后设置本字段的赋值成功标识
循环所有标签,判断不满足“must”标签的情况,进行相应处理
4、辅助方法说明
I、获取实际的Type和Value
在go中,如果一个参数是 *Struct类型的,也就是指针类型,当用这个方法去调用方法获取结构体属性的时候会报错,比如我这边通过一个指针类型去调用FieldByName方法,就会出现下面的错误:
--- FAIL: TestIndirect (0.00s)
panic: reflect: FieldByName of non-struct type [recovered]
panic: reflect: FieldByName of non-struct type
在Go当中,如果是指针类型,可以通过【.Elem】方法获取对应的实际值。所以这边需要两个方法:
1、通过type判断是否指针类型,返回具体的结构体类型
2、通过value判断是否指针类型,发挥具体的结构体数据。
就是下面我们的两个方法:
func indirect(reflectValue reflect.Value) reflect.Value {
for reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
return reflectValue
}
func indirectType(reflectType reflect.Type) reflect.Type {
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
reflectType = reflectType.Elem()
}
return reflectType
}
II、Tag处理
tag的处理代码比较简单,这边分成两个方法,分别是:
1、解析tag字符串
2、解析Field对应的tag,获得每个字段对应的tag条件
解析tag字符串这边,通过【,】分隔进行字符串的处理,flag通过二级制的方式进行处理,这也是我们经常在代码里面用到的方式,具体可以查看一下我的二进制工具的文章:自定义比特工具。
// parseTags Parses struct tags and returns uint8 bit flags.
func parseTags(tag string) (flags uint8) {
for _, t := range strings.Split(tag, ",") {
switch t {
case "-":
flags = tagIgnore
return
case "must":
flags = flags | tagMust
case "nopanic":
flags = flags | tagNoPanic
}
}
return
}
解析tag标签字符串是tag处理的一部分,下面这个方法就是处理Field对应和tag的关系,返回的是一个map
// getBitFlags Parses struct tags for bit flags.
func getBitFlags(toType reflect.Type) map[string]uint8 {
//read note 存储的结构是 FieldName->tag对应的二进制数据(tag标签转换成程序标识)
flags := map[string]uint8{}
//read note 根据结构体的类型获取对应的Field切片
toTypeFields := deepFields(toType)
// Get a list dest of tags
//read note 循环Field切片,获取切片对应的tag数据
for _, field := range toTypeFields {
tags := field.Tag.Get("copier") //tag标签是【copier】
if tags != "" {
//read note tag标签转换成程序处理标识(这边也是使用二进制的处理方式)
flags[field.Name] &