feat(Go-StudyExample):

2021/01/07:新增copier源码阅读和单测代码
This commit is contained in:
Huangzj
2021-01-07 09:23:37 +08:00
parent 9c93a35685
commit b5a1909eb0
8 changed files with 567 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
validate.V8 源码分析文章:https://blog.csdn.net/qq_34326321/article/details/111030128
container源码分析文章:https://blog.csdn.net/qq_34326321/article/details/110804660
copier源码分析文章:https://blog.csdn.net/qq_34326321/article/details/112177087
@@ -0,0 +1,3 @@
guard 'gotest' do
watch(%r{\.go$})
end
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Jinzhu
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,130 @@
# Copier
I am a copier, I copy everything from one to another
[![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions)
## Features
* Copy from field to field with same name
* Copy from method to field with same name
* Copy from field to method with same name
* Copy from slice to slice
* Copy from struct to slice
* Copy from map to map
* Enforce copying a field with a tag
* Ignore a field with a tag
## Usage
```go
package main
import (
"fmt"
"github.com/jinzhu/copier"
)
type User struct {
Name string
Role string
Age int32
// Explicitly ignored in the destination struct.
Salary int
}
func (user *User) DoubleAge() int32 {
return 2 * user.Age
}
// Tags in the destination Struct provide instructions to copier.Copy to ignore
// or enforce copying and to panic or return an error if a field was not copied.
type Employee struct {
// Tell copier.Copy to panic if this field is not copied.
Name string `copier:"must"`
// Tell copier.Copy to return an error if this field is not copied.
Age int32 `copier:"must,nopanic"`
// Tell copier.Copy to explicitly ignore copying this field.
Salary int `copier:"-"`
DoubleAge int32
EmployeId int64
SuperRule string
}
func (employee *Employee) Role(role string) {
employee.SuperRule = "Super " + role
}
func main() {
var (
user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000}
users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}}
employee = Employee{Salary: 150000}
employees = []Employee{}
)
copier.Copy(&employee, &user)
fmt.Printf("%#v \n", employee)
// Employee{
// Name: "Jinzhu", // Copy from field
// Age: 18, // Copy from field
// Salary:150000, // Copying explicitly ignored
// DoubleAge: 36, // Copy from method
// EmployeeId: 0, // Ignored
// SuperRule: "Super Admin", // Copy to method
// }
// Copy struct to slice
copier.Copy(&employees, &user)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"}
// }
// Copy slice to slice
employees = []Employee{}
copier.Copy(&employees, &users)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"},
// {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeId: 0, SuperRule: "Super Dev"},
// }
// Copy map to map
map1 := map[int]int{3: 6, 4: 8}
map2 := map[int32]int8{}
copier.Copy(&map2, map1)
fmt.Printf("%#v \n", map2)
// map[int32]int8{3:6, 4:8}
}
```
### Copy with Option
```go
copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true})
```
## Contributing
You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
# Author
**jinzhu**
* <http://github.com/jinzhu>
* <wosmvp@gmail.com>
* <http://twitter.com/zhangjinzhu>
## License
Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License).
@@ -0,0 +1,398 @@
package copier
import (
"database/sql"
"errors"
"fmt"
"reflect"
"strings"
)
// These flags define options for tag handling
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
)
// Copy copy things
func Copy(toValue interface{}, fromValue interface{}) (err error) {
return copy(toValue, fromValue, false)
}
type Option struct {
IgnoreEmpty bool
}
// CopyWithOption copy with option
func CopyWithOption(toValue interface{}, fromValue interface{}, option Option) (err error) {
return copy(toValue, fromValue, option.IgnoreEmpty)
}
func copy(toValue interface{}, fromValue interface{}, ignoreEmpty bool) (err error) {
var (
isSlice bool
amount = 1
from = indirect(reflect.ValueOf(fromValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值
to = indirect(reflect.ValueOf(toValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值
)
//read note 不可寻址的数据类型可以参考:https://github.com/hyper0x/Golang_Puzzlers/blob/master/src/puzzlers/article15/q1/demo35.go
if !to.CanAddr() {
return errors.New("copy to value is unaddressable")
}
// Return is from value is invalid
if !from.IsValid() {
return
}
//read note 返回参数的具体类型
fromType := indirectType(from.Type())
toType := indirectType(to.Type())
// Just set it if possible to assign
// And need to do copy anyway if the type is struct
//read note 判断是否非结构体并且可以直接赋值
if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) {
to.Set(from)
return
}
//read note from和to都是map
if fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map {
//read note 判断map的key的结构类型是否可以转换,因为这边已经判断是map,所以直接通过 .key来获取,不担心是否报错
if !fromType.Key().ConvertibleTo(toType.Key()) {
return
}
//read note 判断要转换的Map是否为空,为空则进行初始化
if to.IsNil() {
to.Set(reflect.MakeMapWithSize(toType, from.Len()))
}
//read note 遍历Map的所有key
for _, k := range from.MapKeys() {
//read note 根据to的key类型创建一个新的key
toKey := indirect(reflect.New(toType.Key()))
//read note 设置key的值
if !set(toKey, k) {
continue
}
//read note 设置value值
toValue := indirect(reflect.New(toType.Elem()))
if !set(toValue, from.MapIndex(k)) {
//read note 对嵌套的结构体进行copy
err = Copy(toValue.Addr().Interface(), from.MapIndex(k).Interface())
if err != nil {
continue
}
}
to.SetMapIndex(toKey, toValue)
}
}
//read note 只要有一个数据结构不是结构体直接返回?
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
return
}
//read note 切片处理:设置切片的长度
//read note 如果Result是Slice类型
if to.Kind() == reflect.Slice {
//read note 设置isSlice
isSlice = true
//read note amount初始是1,如果被复制的对象也是数组,则这边修改成数组长度,否则1表示只处理结构体
if from.Kind() == reflect.Slice {
amount = from.Len()
}
}
//read note 循环被复制的切片的长度
for i := 0; i < amount; i++ {
var dest, source reflect.Value
//read note Result的结果是数组
if isSlice {
// source
// read note 如果Origin的类型是数组,需要根据index进行获取结构体
if from.Kind() == reflect.Slice {
source = indirect(from.Index(i))
} else {
// read note 如果Origin的类型是结构体,直接获取该结构体
source = indirect(from)
}
// dest
dest = indirect(reflect.New(toType).Elem())
} else {
//read note Result的结果不是数组,直接获取结构体
source = indirect(from)
dest = indirect(to)
}
// Get tag options
//read note 获取tag的所有标签
tagBitFlags := map[string]uint8{}
if dest.IsValid() {
//read note 根据结构体type获取所有的Field对应的tag标签
tagBitFlags = getBitFlags(toType)
}
// check source
//read note 如果是非零值
if source.IsValid() {
//read note 获取结构体的所有Field的信息(数组)
fromTypeFields := deepFields(fromType)
// fmt.Printf("%#v", fromTypeFields)
// Copy from field to field or method
//todo 循环所有的Field处理
for _, field := range fromTypeFields {
name := field.Name
// Get bit flags for field
//read note 根据name获取tag数据
fieldFlags, _ := tagBitFlags[name]
// Check if we should ignore copying
//read note ignore标签对结构体的影响处理
if (fieldFlags & tagIgnore) != 0 {
continue
}
//read note Origin的方法和Result字段同名的处理
if fromField := source.FieldByName(name); fromField.IsValid() && !shouldIgnore(fromField, ignoreEmpty) {
// has field
//read note 根据名称获取Result结构体的字段.
if toField := dest.FieldByName(name); toField.IsValid() {
if toField.CanSet() {
if !set(toField, fromField) {
if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
return err
}
} else {
//read note 赋值完成,设置对应的标识
if fieldFlags != 0 {
// Note that a copy was made
//read note 设置复制标识
tagBitFlags[name] = fieldFlags | hasCopied
}
}
}
} else {
// try to set to method
var toMethod reflect.Value
//read note 通过名称找到对应的Method
if dest.CanAddr() {
toMethod = dest.Addr().MethodByName(name)
} else {
toMethod = dest.MethodByName(name)
}
//read note 【被转换对象的方法调用】的校验还比较严格,这边可以看出来只能有一个字段,并且字段类型要对应上
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
//read note 调用声明的方法
toMethod.Call([]reflect.Value{fromField})
}
}
}
}
//read note Result方法 与Origin字段同名的处理
// Copy from method to field
//read note 处理目标结构体的方法,目标结构体的方法要和被复制结构体的字段名一致,就是这边控制的
for _, field := range deepFields(toType) {
name := field.Name
//read note 根据Result的字段,获取Origin同名的方法
var fromMethod reflect.Value
if source.CanAddr() {
fromMethod = source.Addr().MethodByName(name)
} else {
fromMethod = source.MethodByName(name)
}
//read note 如果方法符合规则,没有入参,有一个出参,则进行对应方法的调用处理
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, ignoreEmpty) {
if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
values := fromMethod.Call([]reflect.Value{})
if len(values) >= 1 {
//read note 进行字段的设值
set(toField, values[0])
}
}
}
}
}
//read note 转换结果Result是切片的处理:分成两种情况,被复制的是 结构体指针 和 结构体
if isSlice {
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest.Addr()))
} else if dest.Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest))
}
}
//read note 这边是不是会有一个问题,就是err是不是会被覆盖,前面的字段有错误,最后一个没有错误则会覆盖之前的error
err = checkBitFlags(tagBitFlags)
}
return
}
func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool {
if !ignoreEmpty {
return false
}
return v.IsZero()
}
//read note 根据结构体类型,获取结构体对应的Field切片,注意这边的【Anonymous】表示的匿名变量,匿名变量的Field这边需要特殊处理
func deepFields(reflectType reflect.Type) []reflect.StructField {
var fields []reflect.StructField
//read note 判断是不是结构体,只能对结构体进行处理
if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
//read note 循环处理对应的所有Field
for i := 0; i < reflectType.NumField(); i++ {
v := reflectType.Field(i)
//read note 对【嵌入(匿名)字段】结构体 的所有结构体进行添加
if v.Anonymous {
fields = append(fields, deepFields(v.Type)...)
} else {
fields = append(fields, v)
}
}
}
return fields
}
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
}
func set(to, from reflect.Value) bool {
//read note IsValid返回是否非零值的结果.所以这边的处理是针对非零值
if from.IsValid() {
//read note 前置条件:处理to的类型,如果from为空,则直接设置空值返回
// to是指针类型特殊处理
if to.Kind() == reflect.Ptr {
// set `to` to nil if from is nil
//read note 如果from是空,则直接设置to为零值返回
if from.Kind() == reflect.Ptr && from.IsNil() {
to.Set(reflect.Zero(to.Type()))
return true
} else if to.IsNil() {
//read note 如果to是nil且不满足上面的from为nil的条件,这个时候要给to设置默认值
to.Set(reflect.New(to.Type().Elem()))
}
//read note 指针的转换处理
to = to.Elem()
}
//read note from和to类型的转换处理,这边当from是ptr类型的时候,会调用set进行递归处理
//read note 如果类型可以进行转换,则要设置对应的值(具体什么类型可以转换需要看一下源码,这里不多赘述)
if from.Type().ConvertibleTo(to.Type()) {
to.Set(from.Convert(to.Type()))
} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
//read note sql.Scanner 这个不知道具体是干嘛的.
err := scanner.Scan(from.Interface())
if err != nil {
return false
}
} else if from.Kind() == reflect.Ptr {
//read note from是指针类型,处理成结构体进行赋值(相当于递归再往下走)
return set(to, from.Elem())
} else {
//read note 其他不能转换的直接返回false
return false
}
}
//read note 零值直接返回true,零值不处理
return true
}
// 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
}
// 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] = parseTags(tags)
}
}
return flags
}
// checkBitFlags Checks flags for error or panic conditions.
func checkBitFlags(flagsList map[string]uint8) (err error) {
// Check flag conditions were met
//read note 循环mapFieldName->tag对应的二进制数据)
for name, flags := range flagsList {
//read note 如果字段没有被复制
if flags&hasCopied == 0 {
switch {
case flags&tagMust != 0 && flags&tagNoPanic != 0:
//read note 处理1:返回错误信息
err = fmt.Errorf("Field %s has must tag but was not copied", name)
return
case flags&(tagMust) != 0:
//read note 处理2:直接报错
panic(fmt.Sprintf("Field %s has must tag but was not copied", name))
}
}
}
return
}
@@ -0,0 +1,3 @@
module github.com/jinzhu/copier
go 1.15