From 157ccc4793cce796a9ef5841851428329e9a00fe Mon Sep 17 00:00:00 2001 From: tom twinkle <47764757+tomtwinkle@users.noreply.github.com> Date: Fri, 5 Mar 2021 20:20:25 +0900 Subject: [PATCH 1/2] merge jinzhu/copier master From 51599a9e0488a510232dfd508e84256d8724874a Mon Sep 17 00:00:00 2001 From: tom twinkle <47764757+tomtwinkle@users.noreply.github.com> Date: Fri, 5 Mar 2021 22:17:31 +0900 Subject: [PATCH 2/2] Added registration for conversion process to different type --- copier.go | 98 ++++++++++++-- copier_type_copier_test.go | 258 +++++++++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 2 + 4 files changed, 347 insertions(+), 13 deletions(-) create mode 100644 copier_type_copier_test.go create mode 100644 go.sum diff --git a/copier.go b/copier.go index 26276c0..70d9999 100644 --- a/copier.go +++ b/copier.go @@ -4,6 +4,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/golang/groupcache/lru" "reflect" "strings" ) @@ -32,17 +33,65 @@ type Option struct { DeepCopy bool } +type TypePair struct { + SrcType reflect.Type + DstType reflect.Type +} + +type TypedCopier interface { + Copy(dstValue, srcValue reflect.Value) error + Pairs() []TypePair +} + +type Copier interface { + Copy(toValue interface{}, fromValue interface{}) (err error) + CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) + Register(copiers ...TypedCopier) +} + +type copy struct { + typeCache *lru.Cache +} + +// Instantiation to add original conversion processing for each type pair +func NewCopier() Copier { + return ©{ + typeCache: lru.New(1000), + } +} + // Copy copy things func Copy(toValue interface{}, fromValue interface{}) (err error) { - return copier(toValue, fromValue, Option{}) + c := NewCopier() + return c.Copy(toValue, fromValue) } // CopyWithOption copy with option func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { - return copier(toValue, fromValue, opt) + c := NewCopier() + return c.CopyWithOption(toValue, fromValue, opt) +} + +// Copy copy things +func (c copy) Copy(toValue interface{}, fromValue interface{}) (err error) { + return c.copier(toValue, fromValue, Option{}) +} + +// CopyWithOption copy with option +func (c copy) CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err error) { + return c.copier(toValue, fromValue, opt) +} + +// Register TypedCopier +func (c *copy) Register(copiers ...TypedCopier) { + for _, co := range copiers { + for _, pair := range co.Pairs() { + c.typeCache.Add(pair, co) + } + } } -func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { +func (c copy) copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { var ( isSlice bool amount = 1 @@ -93,14 +142,14 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) for _, k := range from.MapKeys() { toKey := indirect(reflect.New(toType.Key())) - if !set(toKey, k, opt.DeepCopy) { + if !c.set(toKey, k, opt.DeepCopy) { return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key()) } elemType, _ := indirectType(toType.Elem()) toValue := indirect(reflect.New(elemType)) - if !set(toValue, from.MapIndex(k), opt.DeepCopy) { - if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { + if !c.set(toValue, from.MapIndex(k), opt.DeepCopy) { + if err = c.copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil { return err } } @@ -123,8 +172,8 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) to.Set(slice) } for i := 0; i < from.Len(); i++ { - if !set(to.Index(i), from.Index(i), opt.DeepCopy) { - err = CopyWithOption(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) + if !c.set(to.Index(i), from.Index(i), opt.DeepCopy) { + err = c.CopyWithOption(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt) if err != nil { continue } @@ -222,8 +271,8 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) toField := dest.FieldByName(name) if toField.IsValid() { if toField.CanSet() { - if !set(toField, fromField, opt.DeepCopy) { - if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { + if !c.set(toField, fromField, opt.DeepCopy) { + if err := c.copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil { return err } } else { @@ -264,7 +313,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { values := fromMethod.Call([]reflect.Value{}) if len(values) >= 1 { - set(toField, values[0], opt.DeepCopy) + c.set(toField, values[0], opt.DeepCopy) } } } @@ -329,7 +378,15 @@ func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) { return reflectType, isPtr } -func set(to, from reflect.Value, deepCopy bool) bool { +func (c copy) set(to, from reflect.Value, deepCopy bool) bool { + if from.IsValid() && from.IsValid() { + if ok, err := c.typedCopyFunc(to, from); err != nil { + return false + } else if ok { + return true + } + } + if from.IsValid() { if to.Kind() == reflect.Ptr { // set `to` to nil if from is nil @@ -403,7 +460,7 @@ func set(to, from reflect.Value, deepCopy bool) bool { to.Set(rv) } } else if from.Kind() == reflect.Ptr { - return set(to, from.Elem(), deepCopy) + return c.set(to, from.Elem(), deepCopy) } else { return false } @@ -412,6 +469,21 @@ func set(to, from reflect.Value, deepCopy bool) bool { return true } +func (c copy) typedCopyFunc(to, from reflect.Value) (copied bool, err error) { + pair := TypePair{ + SrcType: from.Type(), + DstType: to.Type(), + } + if cpr, ok := c.typeCache.Get(pair); ok { + copier := cpr.(TypedCopier) + if err := copier.Copy(to, from); err != nil { + return false, err + } + return true, nil + } + return false, nil +} + // parseTags Parses struct tags and returns uint8 bit flags. func parseTags(tag string) (flags uint8) { for _, t := range strings.Split(tag, ",") { diff --git a/copier_type_copier_test.go b/copier_type_copier_test.go new file mode 100644 index 0000000..e744b5c --- /dev/null +++ b/copier_type_copier_test.go @@ -0,0 +1,258 @@ +package copier_test + +import ( + "errors" + "github.com/jinzhu/copier" + "reflect" + "strconv" + "testing" + "time" +) + +type intToStringCopier struct{} + +func (s intToStringCopier) Pairs() []copier.TypePair { + return []copier.TypePair{ + { + SrcType: reflect.TypeOf(0), + DstType: reflect.TypeOf(""), + }, + } +} + +func (s intToStringCopier) Copy(dst, src reflect.Value) error { + val, ok := src.Interface().(int) + if !ok { + return errors.New("type not match") + } + str := strconv.Itoa(val) + dst.Set(reflect.ValueOf(str)) + return nil +} + +type timeToStringCopier struct{} + +func (t timeToStringCopier) Pairs() []copier.TypePair { + return []copier.TypePair{ + { + SrcType: reflect.TypeOf(time.Time{}), + DstType: reflect.TypeOf(""), + }, + { + SrcType: reflect.TypeOf(&time.Time{}), + DstType: reflect.TypeOf(""), + }, + } +} + +func (t timeToStringCopier) Copy(dst, src reflect.Value) error { + const timeFormat = "2006-01-02T15:04:05.999999999Z07:00" + if src.Kind() == reflect.Ptr && src.IsNil() { + if dst.Kind() == reflect.Ptr { + dst.Set(reflect.Zero(reflect.TypeOf(""))) + } + return nil + } + + var val string + if src.Kind() == reflect.Ptr { + s, ok := src.Interface().(*time.Time) + if !ok { + return errors.New("type not match") + } + val = s.Format(timeFormat) + } else { + s, ok := src.Interface().(time.Time) + if !ok { + return errors.New("type not match") + } + val = s.Format(timeFormat) + } + dst.Set(reflect.ValueOf(val)) + return nil +} + +func TestCopy_Register(t *testing.T) { + type SrcStruct1 struct { + Field1 int + Field2 time.Time + Field3 *time.Time + } + + type DestStruct1 struct { + Field1 string + Field2 string + Field3 string + } + + type SrcStruct2 struct { + Field1 SrcStruct1 + Field2 *SrcStruct1 + Field3 []SrcStruct1 + Field4 []*SrcStruct1 + Field5 map[int]SrcStruct1 + Field6 map[int]*SrcStruct1 + } + + type DestStruct2 struct { + Field1 DestStruct1 + Field2 *DestStruct1 + Field3 []DestStruct1 + Field4 []*DestStruct1 + Field5 map[int]DestStruct1 + Field6 map[int]*DestStruct1 + } + + t.Run("copy different types", func(t *testing.T) { + c := copier.NewCopier() + c.Register(&intToStringCopier{}) + c.Register(&timeToStringCopier{}) + + testTime := time.Date(2021, 3, 5, 1, 30, 0, 123000000, time.UTC) + src := SrcStruct1{ + Field1: 100, + Field2: testTime, + Field3: &testTime, + } + var dst DestStruct1 + + err := c.Copy(&dst, src) + if err != nil { + t.Error("copy fail") + return + } + if dst.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if dst.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if dst.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + }) + + t.Run("copy different types in map, slice, struct", func(t *testing.T) { + c := copier.NewCopier() + c.Register(&intToStringCopier{}) + c.Register(&timeToStringCopier{}) + + testTime := time.Date(2021, 3, 5, 1, 30, 0, 123000000, time.UTC) + src := SrcStruct2{ + Field1: SrcStruct1{ + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + Field2: &SrcStruct1{ + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + Field3: []SrcStruct1{ + { + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + }, + Field4: []*SrcStruct1{ + { + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + }, + Field5: map[int]SrcStruct1{ + 1: { + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + }, + Field6: map[int]*SrcStruct1{ + 1: { + Field1: 100, + Field2: testTime, + Field3: &testTime, + }, + }, + } + var dst DestStruct2 + + err := c.Copy(&dst, src) + if err != nil { + t.Error("copy fail") + return + } + + if dst.Field1.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if dst.Field1.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if dst.Field1.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + + if dst.Field2.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if dst.Field2.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if dst.Field2.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + + for _, f := range dst.Field3 { + if f.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if f.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if f.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + } + + for _, f := range dst.Field4 { + if f.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if f.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if f.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + } + + for _, f := range dst.Field5 { + if f.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if f.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if f.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + } + + for _, f := range dst.Field6 { + if f.Field1 != "100" { + t.Errorf("TestCopy_RegisterField: copy Field1 failed [%v]", dst.Field1) + } + if f.Field2 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field2 failed [%v]", dst.Field2) + } + if f.Field3 != "2021-03-05T01:30:00.123Z" { + t.Errorf("TestCopy_RegisterField: copy Field3 failed [%v]", dst.Field3) + } + } + + }) +} diff --git a/go.mod b/go.mod index 531422d..a64eb0f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/jinzhu/copier go 1.15 + +require github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b23d4f7 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=