文章目录
写在前面
- go每日一库参考地址:https://segmentfault.com/a/1190000021997004
- merge包github地址:https://github.com/imdario/mergo
- 获取命令: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