From 01442ae183d7893f18b609e17f5ae6eee5817a05 Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Thu, 10 Aug 2023 16:14:41 +0200 Subject: [PATCH 1/2] Add valuer interface --- copier.go | 29 +++++++++++++++++++++++++++++ copier_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/copier.go b/copier.go index 43a14f1..cdf368f 100644 --- a/copier.go +++ b/copier.go @@ -94,6 +94,15 @@ type FieldNameMapping struct { Mapping map[string]string } +// Valuer lets custom types implement a function returning the actual value to copy. +// For example if your type is a wrapper, or if it doesn't have to implement `sql/driver.Valuer`, +// you can implement this interface so the returned value will be used instead. It can also be used +// to format your type or convert it to another one before being copied. +// This also enables conversion for types using generics, as you cannot use them with `TypeConverter`. +type Valuer interface { + CopyValue() interface{} +} + // Tag Flags type flags struct { BitFlags map[string]uint8 @@ -118,6 +127,11 @@ func CopyWithOption(toValue interface{}, fromValue interface{}, opt Option) (err } func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) { + + if fromCopyValuer, ok := fromValue.(Valuer); ok { + fromValue = fromCopyValuer.CopyValue() + } + var ( isSlice bool amount = 1 @@ -558,6 +572,11 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ if !from.IsValid() { return true, nil } + + if fromCopyValuer, ok := copyValuer(from); ok { + from = reflect.ValueOf(fromCopyValuer.CopyValue()) + } + if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil { return false, err } else if ok { @@ -819,6 +838,16 @@ func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) { return } +func copyValuer(v reflect.Value) (i Valuer, ok bool) { + if !v.CanAddr() { + i, ok = v.Interface().(Valuer) + return + } + + i, ok = v.Addr().Interface().(Valuer) + return +} + func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value { if caseSensitive { return v.FieldByName(name) diff --git a/copier_test.go b/copier_test.go index 4769eba..f922ee0 100644 --- a/copier_test.go +++ b/copier_test.go @@ -1762,3 +1762,35 @@ func TestNestedNilPointerStruct(t *testing.T) { t.Errorf("to (%v) value should equal from (%v) value", to.Title, from.Title) } } + +type testValuer struct { + Value interface{} +} + +func (v testValuer) CopyValue() interface{} { + return v.Value +} + +func TestCopyValuer(t *testing.T) { + + to := struct { + Value string + }{ + Value: "initial", + } + + from := struct { + Value testValuer + }{ + Value: testValuer{Value: "override"}, + } + + err := copier.Copy(&to, from) + if err != nil { + t.Errorf("should not error: %v", err) + } + + if to.Value != from.Value.Value { + t.Errorf("to (%v) value should equal to from (%v) value", to.Value, from.Value.Value) + } +} From ca7f7fa385a2d8b1f4ff07d1ea47c676f9c3325f Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Thu, 10 Aug 2023 16:32:12 +0200 Subject: [PATCH 2/2] Update README for new Valuer interface --- README.md | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 079dc57..95a6f61 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,20 @@ import ( "github.com/jinzhu/copier" ) +type Address struct{ + City string + Street string +} + +// copier.Valuer interface lets custom types implement a function returning the actual value to copy. +// For example if your type is a wrapper, or if it doesn't have to implement `sql/driver.Valuer`, +// you can implement this interface so the returned value will be used instead. It can also be used +// to format your type or convert it to another one before being copied. +// This also enables conversion for types using generics, as you cannot use them with `TypeConverter`. +func (a Address) CopyValue() interface{} { + return fmt.Sprintf("%s, %s", a.Street, a.City) +} + type User struct { Name string Role string @@ -34,6 +48,8 @@ type User struct { // Explicitly ignored in the destination struct. Salary int + + Address Address } func (user *User) DoubleAge() int32 { @@ -55,6 +71,8 @@ type Employee struct { DoubleAge int32 EmployeeId int64 `copier:"EmployeeNum"` // specify field name SuperRole string + + Address string } func (employee *Employee) Role(role string) { @@ -63,8 +81,10 @@ func (employee *Employee) Role(role string) { 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}} + user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000, Address: Address{Street: "123 Main Street", City: "Somewhere"}} + users = []User{ + {Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000, Address: Address{Street: "124 Secondary Street", City: "SomewhereElse"}}, + {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000, Address: Address{Street: "125 Secondary Street", City: "SomewhereElse"}}} employee = Employee{Salary: 150000} employees = []Employee{} ) @@ -73,12 +93,13 @@ func main() { 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 - // SuperRole: "Super Admin", // Copy to method + // Name: "Jinzhu", // Copy from field + // Age: 18, // Copy from field + // Salary:150000, // Copying explicitly ignored + // DoubleAge: 36, // Copy from method + // EmployeeId: 0, // Ignored + // SuperRole: "Super Admin", // Copy to method + // Address: "123 Main Street, Somewhere", // Copy from value returned by CopyValue() // } // Copy struct to slice @@ -86,7 +107,7 @@ func main() { fmt.Printf("%#v \n", employees) // []Employee{ - // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"} + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin", Address: "123 Main Street, Somewhere"} // } // Copy slice to slice @@ -95,8 +116,8 @@ func main() { fmt.Printf("%#v \n", employees) // []Employee{ - // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"}, - // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeeId: 0, SuperRole: "Super Dev"}, + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin", Address: "124 Secondary Street, SomewhereElse"}, + // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeeId: 0, SuperRole: "Super Dev", Address: "125 Secondary Street, SomewhereElse"}, // } // Copy map to map