diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d742b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +ttt/ diff --git a/copier.go b/copier.go index 5fba6b8..2eecd0d 100644 --- a/copier.go +++ b/copier.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strings" + "sync" "unicode" ) @@ -41,6 +42,22 @@ type Option struct { Converters []TypeConverter } +func (opt Option) converters() map[converterPair]TypeConverter { + var converters = map[converterPair]TypeConverter{} + + // save converters into map for faster lookup + for i := range opt.Converters { + pair := converterPair{ + SrcType: reflect.TypeOf(opt.Converters[i].SrcType), + DstType: reflect.TypeOf(opt.Converters[i].DstType), + } + + converters[pair] = opt.Converters[i] + } + + return converters +} + type TypeConverter struct { SrcType interface{} DstType interface{} @@ -81,23 +98,9 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) amount = 1 from = indirect(reflect.ValueOf(fromValue)) to = indirect(reflect.ValueOf(toValue)) - converters map[converterPair]TypeConverter + converters = opt.converters() ) - // save convertes into map for faster lookup - for i := range opt.Converters { - if converters == nil { - converters = make(map[converterPair]TypeConverter) - } - - pair := converterPair{ - SrcType: reflect.TypeOf(opt.Converters[i].SrcType), - DstType: reflect.TypeOf(opt.Converters[i].DstType), - } - - converters[pair] = opt.Converters[i] - } - if !to.CanAddr() { return ErrInvalidCopyDestination } @@ -394,7 +397,17 @@ func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { return ignoreEmpty && v.IsZero() } +var deepFieldsLock sync.RWMutex +var deepFieldsMap = make(map[reflect.Type][]reflect.StructField) + func deepFields(reflectType reflect.Type) []reflect.StructField { + deepFieldsLock.RLock() + cache, ok := deepFieldsMap[reflectType] + deepFieldsLock.RUnlock() + if ok { + return cache + } + var res []reflect.StructField if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct { fields := make([]reflect.StructField, 0, reflectType.NumField()) @@ -411,11 +424,13 @@ func deepFields(reflectType reflect.Type) []reflect.StructField { } } } - - return fields + res = fields } - return nil + deepFieldsLock.Lock() + deepFieldsMap[reflectType] = res + deepFieldsLock.Unlock() + return res } func indirect(reflectValue reflect.Value) reflect.Value { @@ -537,7 +552,6 @@ func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converter if cnv, ok := converters[pair]; ok { result, err := cnv.Fn(from.Interface()) - if err != nil { return false, err } @@ -682,7 +696,6 @@ func getFieldName(fieldName string, flgs flags) (srcFieldName string, destFieldN } func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { - if !v.CanAddr() { i, ok = v.Interface().(driver.Valuer) return diff --git a/copier_converter_test.go b/copier_converter_test.go index 08bd038..42bc130 100644 --- a/copier_converter_test.go +++ b/copier_converter_test.go @@ -67,7 +67,6 @@ func TestCopyWithTypeConverters(t *testing.T) { }, }, }) - if err != nil { t.Fatalf(`Should be able to copy from src to dst object. %v`, err) return @@ -127,7 +126,6 @@ func TestCopyWithConverterAndAnnotation(t *testing.T) { }, }, }) - if err != nil { t.Fatalf(`Should be able to copy from src to dst object. %v`, err) return @@ -175,7 +173,6 @@ func TestCopyWithConverterStrToStrPointer(t *testing.T) { }, }, }) - if err != nil { t.Fatalf(`Should be able to copy from src to dst object. %v`, err) return diff --git a/copier_test.go b/copier_test.go index f6d68a4..2425476 100644 --- a/copier_test.go +++ b/copier_test.go @@ -179,7 +179,9 @@ func TestCopyFromStructToSlice(t *testing.T) { } func TestCopyFromSliceToSlice(t *testing.T) { - users := []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, {Name: "Jinzhu2", Age: 22, Role: "Dev", Notes: []string{"hello world", "hello"}}} + users := []User{ + {Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, + {Name: "Jinzhu2", Age: 22, Role: "Dev", Notes: []string{"hello world", "hello"}}} employees := []Employee{} if copier.Copy(&employees, users); len(employees) != 2 { @@ -786,7 +788,7 @@ func TestMapInterface(t *testing.T) { t.Run("Test copy map with nested slice map", func(t *testing.T) { var out map[string]interface{} - var value = map[string]interface{}{ + value := map[string]interface{}{ "list": []map[string]interface{}{ { "shop_id": 123, @@ -1203,7 +1205,7 @@ func TestCopyMapOfInt(t *testing.T) { func TestCopyMapOfSliceValue(t *testing.T) { // case1: map's value is a simple slice key, value := 2, 3 - src := map[int][]int{key: []int{value} } + src := map[int][]int{key: {value}} dst1 := map[int][]int{} var dst2 map[int][]int err := copier.Copy(&dst1, src) @@ -1220,7 +1222,7 @@ func TestCopyMapOfSliceValue(t *testing.T) { if !ok || len(v1) != len(v2) || k != key { t.Errorf("Map should be copied") } - for i, _ := range v1 { + for i := range v1 { if v2[i] != value { t.Errorf("Map's slice value shoud be copied") } @@ -1240,10 +1242,10 @@ func TestCopyMapOfSliceValue(t *testing.T) { // case2: map's value is a slice whose element is map key1, key2 := 2, 3 value = 4 - s := map[int][]map[int]int{key1: []map[int]int{ {key2: value} } } - d1 := map[int][]map[int]int{key1: []map[int]int{ {key1: key2 } } } - d2 := map[int][]map[int]int{key1: []map[int]int{ } } - d3 := map[int][]map[int]int{key1: nil } + s := map[int][]map[int]int{key1: {{key2: value}}} + d1 := map[int][]map[int]int{key1: {{key1: key2}}} + d2 := map[int][]map[int]int{key1: {}} + d3 := map[int][]map[int]int{key1: nil} d4 := map[int][]map[int]int{} d5 := map[int][]map[int]int(nil) ms := []map[int][]map[int]int{d1, d2, d3, d4, d5} @@ -1267,7 +1269,7 @@ func TestCopyMapOfSliceValue(t *testing.T) { for k, v := range m { if k != key2 || v != value { t.Errorf("Map's slice value should be copied recursively") - } + } } } } @@ -1276,7 +1278,7 @@ func TestCopyMapOfSliceValue(t *testing.T) { func TestCopyMapOfPtrValue(t *testing.T) { intV := 3 intv := intV - src := map[int]*int{2: &intv } + src := map[int]*int{2: &intv} dst1 := map[int]*int{} var dst2 map[int]*int err := copier.Copy(&dst1, src) @@ -1295,7 +1297,7 @@ func TestCopyMapOfPtrValue(t *testing.T) { } v3, ok := dst2[k] - if !ok || v3 == nil ||*v3 != *v1 || *v3 != intV { + if !ok || v3 == nil || *v3 != *v1 || *v3 != intV { t.Errorf("Map should be copied") } } @@ -1351,7 +1353,6 @@ func TestScanner(t *testing.T) { } func TestScanFromPtrToSqlNullable(t *testing.T) { - var ( from struct { S string @@ -1423,7 +1424,7 @@ func TestScanFromPtrToSqlNullable(t *testing.T) { } func TestDeepCopyInterface(t *testing.T) { - var m = make(map[string]string) + m := make(map[string]string) m["a"] = "ccc" from := []interface{}{[]int{7, 8, 9}, 2, 3, m, errors.New("aaaa")} @@ -1520,7 +1521,6 @@ func TestDeepCopyTime(t *testing.T) { } } - func TestNestedPrivateData(t *testing.T) { type hasPrivate struct { data int @@ -1558,7 +1558,6 @@ func TestNestedPrivateData(t *testing.T) { } } - func TestDeepMapCopyTime(t *testing.T) { t1 := time.Now() t2 := t1.Add(time.Second) @@ -1611,7 +1610,7 @@ func TestDeepCopySimpleTime(t *testing.T) { } } -type TimeWrapper struct{ +type TimeWrapper struct { time.Time }