Go源码分析:merge

本文介绍了Go库mergo的源码分析,主要讲解了Merge和Map方法。Merge方法处理结构体间的转换,包括前置处理、结构体、映射和切片类型的处理。Map方法则处理不同类型的结构体转换,涉及循环引用、结构体和映射类型的转换。文章还涵盖了配置、特殊处理和测试代码的细节。
摘要由CSDN通过智能技术生成


在这里插入图片描述

写在前面

  1. go每日一库参考地址:https://segmentfault.com/a/1190000021997004
  2. merge包github地址:https://github.com/imdario/mergo
  3. 获取命令:go get github.com/imdario/mergo

       本文主要是根据go每日一库提供的merge包的测试代码,进而对merge包进行源码的阅读,本文主要分享源码阅读的结果,如有错误,还请指正,仅供参考。

配置

       merge包的配置Config。主要是包含了字段覆盖处理,数组处理,空值处理等一系列的配置信息,比较简单,看一下Config的数据结构:

	//read note 配置,这边采用Option的方式进行传参设置,通过Option修改配置的字段
	type Config struct {
   
		Overwrite                    bool         //read note 是否覆盖
		AppendSlice                  bool         //read note 是否添加到对应数组中
		TypeCheck                    bool         //read note 是否进行类型检查
		Transformers                 Transformers //read note 自定义类型转换
		overwriteWithEmptyValue      bool         //read note 是否覆盖空值
		overwriteSliceWithEmptyValue bool         //read note 是否覆盖空数组
		sliceDeepCopy                bool         //read note 数组中的元素是否进行深度克隆
		debug                        bool
	}

       
       这边对于配置的处理不是采用初始化设置的方式,而是采用BSD风格,通过传入Optional参数来进行设置,来具体看一下这边提供的Optional:

	func WithTransformers(transformers Transformers) func(*Config) {
   
		return func(config *Config) {
   
			config.Transformers = transformers
		}
	}
	
	// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
	func WithOverride(config *Config) {
   
		config.Overwrite = true
	}
	
	// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
	func WithOverwriteWithEmptyValue(config *Config) {
   
		config.Overwrite = true
		config.overwriteWithEmptyValue = true
	}
	
	// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
	func WithOverrideEmptySlice(config *Config) {
   
		config.overwriteSliceWithEmptyValue = true
	}
	
	// WithAppendSlice will make merge append slices instead of overwriting it.
	func WithAppendSlice(config *Config) {
   
		config.AppendSlice = true
	}
	
	// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
	func WithTypeCheck(config *Config) {
   
		config.TypeCheck = true
	}
	
	// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
	func WithSliceDeepCopy(config *Config) {
   
		config.sliceDeepCopy = true
		config.Overwrite = true
	}

       这边还有一个 Transformers 是在转换过程中,对指定的类型进行处理,也就是说可能一些比较特殊的字段或者类型需要特殊处理,这边可以通过设置我们添加的转换方法进行指定的处理。
Transformers 的具体实现如下:

type Transformers interface {
   
	Transformer(reflect.Type) func(dst, src reflect.Value) error
}

       

特殊处理

       首先讲一下这个特殊处理,在Merge的方法里面,有一个判断map中是否存在类型和地址与当前结构体一致的时候,就不会在进行merge了.
       这边的这个处理,主要针对的情况,我在下面的示例中给出:

type example struct {
   
	Example1 *example
	Age      int
	Money    int
}

func Test() {
   
	example1 := example{
   
		Age:   0,
		Money: 0,
	}
	example1.Example1 = &example1
}

       
       当一个对象中的属性引用他自己的话,进行merge的话会重复merge死循环,所以这边通过一个map来存储已经被merge过的字段,当被merge的结构体类型和type与map中的一致时说明该结构体已经被merge过了,这样的话就直接返回,避免进入死循环,具体的代码如下:

//read note 这个地方应该是根据visited判断字段是否被deepMerge
	if dst.CanAddr() {
   
		addr := dst.UnsafeAddr()
		h := 17 * addr
		seen := visited[h]
		typ := dst.Type()
		//read note 这边是为了避免结构体中和结构体同类型的字段赋值同样的值。如果遇到这种情况会跳过赋值,因为已经赋值过了
		for p := seen; p != nil; p = p.next {
   
			if p.ptr == addr && p.typ == typ {
   
				return nil
			}
		}
		// Remember, remember...
		//read note 记录已经被merge的字段
		visited[h] = &visit{
   addr, typ, seen}
	}

       

Merge方法

       Merge方法处理对应的结构体之间的转换,在处理之前会有一些简单的判断处理,这边需要注意的问题就是Merge方法只能Merge结构体类型一致的两个数据,具体看一下下面的前置处理。
       

1、前置处理

       对于Merge来说,限制会比较多一点,比如说两个数据的结构体的类型必须一致
这边的参数说明一下:

dst :merge的目标结构体(结果)
src :merge的源结构体
opts:Optional传入修改配置

       这边先会对dst的类型进行判断,必须是ptr,其实这边可以做一个转换,不过直接限死了
       然后就是对config进行设置
       接下来判断dst和srv的类型是否相同
       再调用deepMerge方法,是我们的主要Merge操作的方法

func merge(dst, src interface{
   }, opts ...func(*Config)) error {
   
	//read note 判断指针类型和结构体类型是否正确
	if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
   
		return ErrNonPointerAgument
	}
	var (
		vDst, vSrc reflect.Value
		err        error
	)

	config := &Config{
   }

	//read note optional设置config 
	for _, opt := range opts {
   
		opt(config)
	}

	if vDst, vSrc, err = resolveValues(dst, src); err != nil {
   
		return err
	}
	//read note 被merge的两个结构体类型必须一样,否则不能进行merge
	if vDst.Type() != vSrc.Type() {
   
		return ErrDifferentArgumentsTypes
	}
	//read note 进行deepMerge
	return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
}

       

2、目标数据类型为struct

       struct的处理,这边有一个方法比较重要,就是hasMergeableFields,这个方法会遍历dst的所有字段,判断是否存在可获取的字段,如果判断结果是true的话,则会对dst的Field进行deepMerge
先来看一下这个方法:

	//read note 判断结构体是否有可导出字段
	func hasMergeableFields(dst reflect.Value) (exported bool) {
   
		for i, n := 0, dst.NumField(); i < n; i++ {
   
			field := dst.Type().Field(i)
			//read note 匿名字段如果是结构体需要判断该结构体中是否有可导出字段
			if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
   
				exported = exported || hasMergeableFields(dst.Field(i))
			} else if isExportedComponent(&field) {
   
				exported = exported || len(field.PkgPath) == 0
			}
		}
		return
	}
	
	//read note 判断字段是否可导出,这边通过pkgPath和字段名首字母大小写确定
	func isExportedComponent(field *reflect.StructField) bool {
   
		pkgPath := field.PkgPath
		//read note 查看一下pkgPath的注释,如果为空字符串表示可导出,否则不可导出
		if len(pkgPath) > 0 {
   
			return false
		}
		//read note 这边也是判断字段是否可导出
		c := field.Name[0]
		if 'a' <= c && c <= 'z' || c == '_' {
   
			return false
		}
		return true
	}

       
       如果判断hasMergeableFields成立的话,会对所有的字段进行deepMerge处理
       如果不成立的话,判断空值是否覆盖等条件进行设值,具体的处理代码如下:

	//read note 这边会遍历结构体的字段,判断是否有可导出的字段,如果有,才会进行Field遍历,实现deepMerge
	if hasMergeableFields(dst) {
   
		for i, n := 0, dst.NumField(); i < n; i++ {
   
			//read note 对每一个字段进行deepMerge操作
			if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
   
				return
			}
		}
	} else {
   
		//read note 空值覆盖或者是有值覆盖,这个条件需要仔细阅读一下
		if (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
   
			dst.Set(src)
		}
	}

       

3、目标数据类型为map

a、初始化Map

       如果dst为nil,但src不是nil,则说明dst需要进行merge处理,这边需要初始化dst:

	//read note 初始化map
	if dst.IsNil() && !src.IsNil() {
   
		dst.Set(reflect.MakeMap(dst.Type()))
	}

b、判断src不是Map

       判断src的类型不是map,如果overwrite设置为true则直接进行设值

		//read note 这个地方按道理如果是两个相同的结构体,字段或者结构体的类型不可能不一样,所以这边有可能进去吗?
		if src.Kind() != reflect.Map {
   
			if overwrite {
   
				dst.Set(src)
			}
			return
		}

c、遍历Map的key

       接下来就是对map的所有key进行遍历处理,具体的处理步骤如下面的标题,其实这边的处理和上面的有点类似,因为map的key可能是struct\ptr等等类型

1、零值覆盖

       这边会判断下面的几个类型,如果值为nil并且overwrite为true的话,就会进行空值覆盖。

	case reflect.Chan, reflect.Func, reflect.Map
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

了-凡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值