improve gconv.Struct* functions for custom types conversion

This commit is contained in:
John
2020-03-06 23:22:08 +08:00
parent 31f19b0eee
commit 7f0163d958
5 changed files with 96 additions and 14 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -48,7 +48,8 @@ var (
)
// Convert converts the variable <i> to the type <t>, the type <t> is specified by string.
// The optional parameter <params> is used for additional parameter passing.
// The optional parameter <params> 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
}
}

View File

@ -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 <value> 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
}

View File

@ -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