diff --git a/.example/other/test.go b/.example/other/test.go index 92e1cfb76..4e9c48ba9 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,10 +1,25 @@ package main import ( - "github.com/gogf/gf/container/garray" + "fmt" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" ) +type MyInt int + +//func (i *MyInt) UnmarshalValue(interface{}) error { +// *i = 10 +// return nil +//} func main() { - arr := garray.NewStrArray(false) - arr.Unique() + type User struct { + Id MyInt + } + user := new(User) + err := gconv.Struct(g.Map{ + "id": 1, + }, user) + fmt.Println(err) + fmt.Println(user) } diff --git a/database/gdb/gdb_unit_z_mysql_model_test.go b/database/gdb/gdb_unit_z_mysql_model_test.go index 4f523a66b..f25fd1d39 100644 --- a/database/gdb/gdb_unit_z_mysql_model_test.go +++ b/database/gdb/gdb_unit_z_mysql_model_test.go @@ -681,6 +681,28 @@ func Test_Model_Struct(t *testing.T) { }) } +func Test_Model_Struct_CustomType(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + type MyInt int + + gtest.Case(t, func() { + type User struct { + Id MyInt + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + err := db.Table(table).Where("id=1").Struct(user) + gtest.Assert(err, nil) + gtest.Assert(user.NickName, "name_1") + gtest.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") + }) +} + func Test_Model_Structs(t *testing.T) { table := createInitTable() defer dropTable(table) diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 54a99ddc3..c34d0a569 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -48,7 +48,8 @@ var ( ) // Convert converts the variable to the type , the type is specified by string. -// The optional parameter is used for additional parameter passing. +// The optional parameter is used for additional necessary parameter for this conversion. +// It supports common types conversion as its conversion based on type name string. func Convert(i interface{}, t string, params ...interface{}) interface{} { switch t { case "int": @@ -121,6 +122,7 @@ func Convert(i interface{}, t string, params ...interface{}) interface{} { case "Duration", "time.Duration": return Duration(i) default: + return i } } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index eade34f9d..46fdeea40 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -242,6 +242,7 @@ func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) (e if !structFieldValue.CanSet() { return nil } + // If any panic, it secondly uses reflect conversion and assignment. defer func() { if recover() != nil { err = bindVarToReflectValue(structFieldValue, value) @@ -250,6 +251,7 @@ func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) (e if empty.IsNil(value) { structFieldValue.Set(reflect.Zero(structFieldValue.Type())) } else { + // It firstly simply assigns the value to the attribute. structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String()))) } return nil @@ -260,7 +262,8 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) (e switch structFieldValue.Kind() { case reflect.Struct: if err := Struct(value, structFieldValue); err != nil { - structFieldValue.Set(reflect.ValueOf(value)) + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) } // Note that the slice element might be type of struct, // so it uses Struct function doing the converting internally. @@ -275,13 +278,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) (e if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() if err := Struct(v.Index(i).Interface(), e); err != nil { - e.Set(reflect.ValueOf(v.Index(i).Interface())) + // Note there's reflect conversion mechanism here. + e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() if err := Struct(v.Index(i).Interface(), e); err != nil { - e.Set(reflect.ValueOf(v.Index(i).Interface())) + // Note there's reflect conversion mechanism here. + e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } a.Index(i).Set(e) } @@ -293,13 +298,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) (e if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() if err := Struct(value, e); err != nil { - e.Set(reflect.ValueOf(value)) + // Note there's reflect conversion mechanism here. + e.Set(reflect.ValueOf(value).Convert(t)) } a.Index(0).Set(e.Addr()) } else { e := reflect.New(t).Elem() if err := Struct(value, e); err != nil { - e.Set(reflect.ValueOf(value)) + // Note there's reflect conversion mechanism here. + e.Set(reflect.ValueOf(value).Convert(t)) } a.Index(0).Set(e) } @@ -311,34 +318,40 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) (e // Assign value with interface Set. // Note that only pointer can implement interface Set. if v, ok := item.Interface().(apiUnmarshalValue); ok { - v.UnmarshalValue(value) + err = v.UnmarshalValue(value) structFieldValue.Set(item) - return nil + return err } elem := item.Elem() if err = bindVarToReflectValue(elem, value); err == nil { structFieldValue.Set(elem.Addr()) } + // It mainly and specially handles the interface of nil value. case reflect.Interface: if value == nil { + // Specially. structFieldValue.Set(reflect.ValueOf((*interface{})(nil))) } else { - structFieldValue.Set(reflect.ValueOf(value)) + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) } default: defer func() { if e := recover(); e != nil { err = errors.New( - fmt.Sprintf(`cannot convert "%d" to type "%s"`, + fmt.Sprintf(`cannot convert value "%d" to type "%s"`, value, structFieldValue.Type().String(), ), ) } }() - structFieldValue.Set(reflect.ValueOf(value)) + // It here uses reflect converting to type of the attribute and assigns + // the result value to the attribute. It might fail and panic if the usual Go + // conversion rules do not allow conversion. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) } return nil } diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index 9ba5fdf9f..767117b87 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -324,6 +324,36 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) { }) } +func Test_Struct_Attr_CustomType1(t *testing.T) { + type MyInt int + type User struct { + Id MyInt + Name string + } + gtest.Case(t, func() { + user := new(User) + err := gconv.Struct(g.Map{"id": 1, "name": "john"}, user) + gtest.Assert(err, nil) + gtest.Assert(user.Id, 1) + gtest.Assert(user.Name, "john") + }) +} + +func Test_Struct_Attr_CustomType2(t *testing.T) { + type MyInt int + type User struct { + Id []MyInt + Name string + } + gtest.Case(t, func() { + user := new(User) + err := gconv.Struct(g.Map{"id": g.Slice{1, 2}, "name": "john"}, user) + gtest.Assert(err, nil) + gtest.Assert(user.Id, g.Slice{1, 2}) + gtest.Assert(user.Name, "john") + }) +} + func Test_Struct_PrivateAttribute(t *testing.T) { type User struct { Id int