diff --git a/copier.go b/copier.go index 0cc6465..d030702 100644 --- a/copier.go +++ b/copier.go @@ -610,9 +610,14 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ } } + // try convert directly if from.Type().ConvertibleTo(to.Type()) { to.Set(from.Convert(to.Type())) - } else if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { + return true, nil + } + + // try Scanner + if toScanner, ok := to.Addr().Interface().(sql.Scanner); ok { // `from` -> `to` // *string -> sql.NullString if from.Kind() == reflect.Ptr { @@ -627,10 +632,13 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ // string -> sql.NullString // set `to` by invoking method Scan(`from`) err := toScanner.Scan(from.Interface()) - if err != nil { - return false, nil + if err == nil { + return true, nil } - } else if fromValuer, ok := driverValuer(from); ok { + } + + // try Valuer + if fromValuer, ok := driverValuer(from); ok { // `from` -> `to` // sql.NullString -> string v, err := fromValuer.Value() @@ -644,16 +652,21 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ rv := reflect.ValueOf(v) if rv.Type().AssignableTo(to.Type()) { to.Set(rv) - } else if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) { + return true, nil + } + if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) { to.Set(rv.Convert(to.Type())) + return true, nil } - } else if from.Kind() == reflect.Ptr { - return set(to, from.Elem(), deepCopy, converters) - } else { return false, nil } - return true, nil + // from is ptr + if from.Kind() == reflect.Ptr { + return set(to, from.Elem(), deepCopy, converters) + } + + return false, nil } // lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field. diff --git a/copier_converter_test.go b/copier_converter_test.go index 6d83ea1..2b4f4b0 100644 --- a/copier_converter_test.go +++ b/copier_converter_test.go @@ -1,7 +1,11 @@ package copier_test import ( + "bytes" + "database/sql/driver" + "encoding/json" "errors" + "reflect" "strconv" "testing" "time" @@ -218,3 +222,60 @@ func TestCopyWithConverterRaisingError(t *testing.T) { return } } + +type IntArray []int + +func (a IntArray) Value() (driver.Value, error) { + return json.Marshal(a) +} + +type Int int + +type From struct { + Data IntArray +} + +type To struct { + Data []byte +} + +type FailedTo struct { + Data []Int +} + +func TestValuerConv(t *testing.T) { + // when the field of struct implement driver.Valuer and cannot convert to dest type directly, + // copier.set() will return a unexpected (true, nil) + + typ1 := reflect.TypeOf(IntArray{}) + typ2 := reflect.TypeOf([]Int{}) + + if typ1 == typ2 || typ1.ConvertibleTo(typ2) || typ1.AssignableTo(typ2) { + // in 1.22 and older, u can not convert typ1 to typ2 + t.Errorf("can not convert %v to %v direct", typ1, typ2) + } + + var ( + from = From{ + Data: IntArray{1, 2, 3}, + } + to To + failedTo FailedTo + ) + if err := copier.Copy(&to, from); err != nil { + t.Fatal(err) + } + if err := copier.Copy(&failedTo, from); err != nil { + t.Fatal(err) + } + + // Testcase1: valuer conv case + if !bytes.Equal(to.Data, []byte(`[1,2,3]`)) { + t.Errorf("can not convert %v to %v using valuer", typ1, typ2) + } + + // Testcase2: fallback case when valuer conv failed + if len(failedTo.Data) != 3 || failedTo.Data[0] != 1 || failedTo.Data[1] != 2 || failedTo.Data[2] != 3 { + t.Errorf("copier failed from %#v to %#v", from, failedTo) + } +}