Go源码解析:copier库

注:该文章源码分析参考 copier

目录

源码包

代码阅读

1、tag标签说明

2、静态参数

3、整体设计

4、辅助方法说明

I、获取实际的Type和Value

II、Tag处理

III、获取结构体Field切片

IV、检查结构体复制结果

V、对结构体进行设值

5、Copy主方法说明

I、参数说明

II、不可寻址

III、非Valid

IV、可转换结构体的转换

V、两个类型都是map的处理

VI、只有一个类型是结构体的处理

VII、判断数组设置标识

VIII、根据amount进行循环处理

测试代码


源码包

包获取路径:  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主方法说明,能够更清晰的理解对应的代码.

大致的设计思路如下:

  1. 不可寻址和Invalid的数据直接报错或者返回

  2. 判断两个数据结构是不是map,进行map的处理

  3. 数组与结构体的处理,按照类型进行数据遍历

    1. 循环所有字段,解析tag

    2. 判断是否忽略不复制,不复制则跳过

    3. 根据字段名进行赋值

    4. 根据方法名(同名)进行赋值

    5. 赋值成功之后设置本字段的赋值成功标识

    6. 循环所有标签,判断不满足“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] &
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

了-凡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值