mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
improve gconv.Struct
This commit is contained in:
@ -224,19 +224,16 @@ func (r Result) Structs(pointer interface{}) (err error) {
|
||||
itemKind = itemType.Kind()
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
var elem reflect.Value
|
||||
if itemKind == reflect.Ptr {
|
||||
e := reflect.New(itemType.Elem()).Elem()
|
||||
if err = r[i].Struct(e); err != nil {
|
||||
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
|
||||
}
|
||||
array.Index(i).Set(e.Addr())
|
||||
elem = reflect.New(itemType.Elem())
|
||||
} else {
|
||||
e := reflect.New(itemType).Elem()
|
||||
if err = r[i].Struct(e); err != nil {
|
||||
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
elem = reflect.New(itemType).Elem()
|
||||
}
|
||||
if err = r[i].Struct(elem); err != nil {
|
||||
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
|
||||
}
|
||||
array.Index(i).Set(elem)
|
||||
}
|
||||
reflect.ValueOf(pointer).Elem().Set(array)
|
||||
return nil
|
||||
|
||||
@ -51,10 +51,6 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
}
|
||||
|
||||
if doStructByDirectReflectSet(params, pointer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflect operation panics.
|
||||
if e := recover(); e != nil {
|
||||
@ -86,13 +82,60 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
paramsReflectValue reflect.Value
|
||||
pointerReflectValue reflect.Value
|
||||
pointerReflectKind reflect.Kind
|
||||
pointerElemReflectValue reflect.Value // The pointed element.
|
||||
)
|
||||
if v, ok := params.(reflect.Value); ok {
|
||||
paramsReflectValue = v
|
||||
} else {
|
||||
paramsReflectValue = reflect.ValueOf(params)
|
||||
}
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
pointerReflectValue = v
|
||||
pointerElemReflectValue = v
|
||||
} else {
|
||||
pointerReflectValue = reflect.ValueOf(pointer)
|
||||
pointerReflectKind = pointerReflectValue.Kind()
|
||||
if pointerReflectKind != reflect.Ptr {
|
||||
return gerror.Newf("object pointer should be type of '*struct', but got '%v'", pointerReflectKind)
|
||||
}
|
||||
// Using IsNil on reflect.Ptr variable is OK.
|
||||
if !pointerReflectValue.IsValid() || pointerReflectValue.IsNil() {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
}
|
||||
pointerElemReflectValue = pointerReflectValue.Elem()
|
||||
}
|
||||
// If `params` and `pointer` are the same type, the do directly assignment.
|
||||
// For performance enhancement purpose.
|
||||
if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() {
|
||||
pointerElemReflectValue.Set(paramsReflectValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue.
|
||||
// Assign value with interface UnmarshalValue.
|
||||
// Note that only pointer can implement interface UnmarshalValue.
|
||||
if v, ok := pointer.(apiUnmarshalValue); ok {
|
||||
if v, ok := pointerReflectValue.Interface().(apiUnmarshalValue); ok {
|
||||
return v.UnmarshalValue(params)
|
||||
}
|
||||
|
||||
// It automatically creates struct object if necessary.
|
||||
// For example, if <pointer> is **User, then <elem> is *User, which is a pointer to User.
|
||||
if pointerElemReflectValue.Kind() == reflect.Ptr {
|
||||
if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() {
|
||||
e := reflect.New(pointerElemReflectValue.Type().Elem()).Elem()
|
||||
pointerElemReflectValue.Set(e.Addr())
|
||||
}
|
||||
if v, ok := pointerElemReflectValue.Interface().(apiUnmarshalValue); ok {
|
||||
return v.UnmarshalValue(params)
|
||||
}
|
||||
// Retrieve its element, may be struct at last.
|
||||
pointerElemReflectValue = pointerElemReflectValue.Elem()
|
||||
}
|
||||
|
||||
// paramsMap is the map[string]interface{} type variable for params.
|
||||
// DO NOT use MapDeep here.
|
||||
paramsMap := Map(params)
|
||||
@ -100,50 +143,6 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
return gerror.Newf("convert params to map failed: %v", params)
|
||||
}
|
||||
|
||||
// Using reflect to do the converting,
|
||||
// it also supports type of reflect.Value for <pointer>(always in internal usage).
|
||||
elem, ok := pointer.(reflect.Value)
|
||||
if !ok {
|
||||
rv := reflect.ValueOf(pointer)
|
||||
if kind := rv.Kind(); kind != reflect.Ptr {
|
||||
return gerror.Newf("object pointer should be type of '*struct', but got '%v'", kind)
|
||||
}
|
||||
// Using IsNil on reflect.Ptr variable is OK.
|
||||
if !rv.IsValid() || rv.IsNil() {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
}
|
||||
elem = rv.Elem()
|
||||
}
|
||||
|
||||
// Check if an invalid interface.
|
||||
if elem.Kind() == reflect.Interface {
|
||||
elem = elem.Elem()
|
||||
if !elem.IsValid() {
|
||||
return gerror.New("interface type converting is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// It automatically creates struct object if necessary.
|
||||
// For example, if <pointer> is **User, then <elem> is *User, which is a pointer to User.
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
if !elem.IsValid() || elem.IsNil() {
|
||||
e := reflect.New(elem.Type().Elem()).Elem()
|
||||
elem.Set(e.Addr())
|
||||
elem = e
|
||||
} else {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalValue checks again.
|
||||
// Assign value with interface UnmarshalValue.
|
||||
// Note that only pointer can implement interface UnmarshalValue.
|
||||
if elem.Kind() == reflect.Struct && elem.CanAddr() {
|
||||
if v, ok := elem.Addr().Interface().(apiUnmarshalValue); ok {
|
||||
return v.UnmarshalValue(params)
|
||||
}
|
||||
}
|
||||
|
||||
// It only performs one converting to the same attribute.
|
||||
// doneMap is used to check repeated converting, its key is the real attribute name
|
||||
// of the struct.
|
||||
@ -155,10 +154,10 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
tempName string
|
||||
elemFieldType reflect.StructField
|
||||
elemFieldValue reflect.Value
|
||||
elemType = elem.Type()
|
||||
elemType = pointerElemReflectValue.Type()
|
||||
attrMap = make(map[string]string)
|
||||
)
|
||||
for i := 0; i < elem.NumField(); i++ {
|
||||
for i := 0; i < pointerElemReflectValue.NumField(); i++ {
|
||||
elemFieldType = elemType.Field(i)
|
||||
// Only do converting to public attributes.
|
||||
if !utils.IsLetterUpper(elemFieldType.Name[0]) {
|
||||
@ -166,7 +165,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
}
|
||||
// Maybe it's struct/*struct embedded.
|
||||
if elemFieldType.Anonymous {
|
||||
elemFieldValue = elem.Field(i)
|
||||
elemFieldValue = pointerElemReflectValue.Field(i)
|
||||
// Ignore the interface attribute if it's nil.
|
||||
if elemFieldValue.Kind() == reflect.Interface {
|
||||
elemFieldValue = elemFieldValue.Elem()
|
||||
@ -189,7 +188,7 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
// The key of the tagMap is the attribute name of the struct,
|
||||
// and the value is its replaced tag name for later comparison to improve performance.
|
||||
tagMap := make(map[string]string)
|
||||
tagToNameMap, err := structs.TagMapName(elem, StructTagPriority)
|
||||
tagToNameMap, err := structs.TagMapName(pointerElemReflectValue, StructTagPriority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -249,27 +248,13 @@ func doStruct(params interface{}, pointer interface{}, mapping ...map[string]str
|
||||
}
|
||||
// Mark it done.
|
||||
doneMap[attrName] = struct{}{}
|
||||
if err := bindVarToStructAttr(elem, attrName, mapV, mapping...); err != nil {
|
||||
if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// doStructByDirectReflectSet do the converting directly using reflect Set.
|
||||
// It returns true if success, or else false.
|
||||
func doStructByDirectReflectSet(params interface{}, pointer interface{}) (ok bool) {
|
||||
v1 := reflect.ValueOf(pointer)
|
||||
v2 := reflect.ValueOf(params)
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if elem := v1.Elem(); elem.IsValid() && elem.Type() == v2.Type() {
|
||||
elem.Set(v2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// bindVarToStructAttr sets value to struct object attribute by name.
|
||||
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping ...map[string]string) (err error) {
|
||||
structFieldValue := elem.FieldByName(name)
|
||||
|
||||
103
util/gconv/gconv_z_unit_struct_interface_test.go
Normal file
103
util/gconv/gconv_z_unit_struct_interface_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gconv_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/crypto/gcrc32"
|
||||
"github.com/gogf/gf/encoding/gbinary"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MyTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
type MyTimeSt struct {
|
||||
ServiceDate MyTime
|
||||
}
|
||||
|
||||
func (st *MyTimeSt) UnmarshalValue(v interface{}) error {
|
||||
m := gconv.Map(v)
|
||||
t, err := gtime.StrToTime(gconv.String(m["ServiceDate"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.ServiceDate = MyTime{t.Time}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_Struct_UnmarshalValue1(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
st := &MyTimeSt{}
|
||||
err := gconv.Struct(g.Map{"ServiceDate": "2020-10-10 12:00:01"}, st)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(st.ServiceDate.Time.Format("2006-01-02 15:04:05"), "2020-10-10 12:00:01")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
st := &MyTimeSt{}
|
||||
err := gconv.Struct(g.Map{"ServiceDate": nil}, st)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
type Pkg struct {
|
||||
Length uint16 // Total length.
|
||||
Crc32 uint32 // CRC32.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NewPkg creates and returns a package with given data.
|
||||
func NewPkg(data []byte) *Pkg {
|
||||
return &Pkg{
|
||||
Length: uint16(len(data) + 6),
|
||||
Crc32: gcrc32.Encrypt(data),
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal encodes the protocol struct to bytes.
|
||||
func (p *Pkg) Marshal() []byte {
|
||||
b := make([]byte, 6+len(p.Data))
|
||||
copy(b, gbinary.EncodeUint16(p.Length))
|
||||
copy(b[2:], gbinary.EncodeUint32(p.Crc32))
|
||||
copy(b[6:], p.Data)
|
||||
return b
|
||||
}
|
||||
|
||||
// UnmarshalValue decodes bytes to protocol struct.
|
||||
func (p *Pkg) UnmarshalValue(v interface{}) error {
|
||||
b := gconv.Bytes(v)
|
||||
if len(b) < 6 {
|
||||
return errors.New("invalid package length")
|
||||
}
|
||||
p.Length = gbinary.DecodeToUint16(b[:2])
|
||||
if len(b) < int(p.Length) {
|
||||
return errors.New("invalid data length")
|
||||
}
|
||||
p.Crc32 = gbinary.DecodeToUint32(b[2:6])
|
||||
p.Data = b[6:]
|
||||
if gcrc32.Encrypt(p.Data) != p.Crc32 {
|
||||
return errors.New("crc32 validation failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_Struct_UnmarshalValue2(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var p1, p2 *Pkg
|
||||
p1 = NewPkg([]byte("123"))
|
||||
err := gconv.Struct(p1.Marshal(), &p2)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(p1, p2)
|
||||
})
|
||||
}
|
||||
@ -853,38 +853,6 @@ func Test_Struct_CatchPanic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
type MyTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
type MyTimeSt struct {
|
||||
ServiceDate MyTime
|
||||
}
|
||||
|
||||
func (st *MyTimeSt) UnmarshalValue(v interface{}) error {
|
||||
m := gconv.Map(v)
|
||||
t, err := gtime.StrToTime(gconv.String(m["ServiceDate"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.ServiceDate = MyTime{t.Time}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_Struct_UnmarshalValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
st := &MyTimeSt{}
|
||||
err := gconv.Struct(g.Map{"ServiceDate": "2020-10-10 12:00:01"}, st)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(st.ServiceDate.Time.Format("2006-01-02 15:04:05"), "2020-10-10 12:00:01")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
st := &MyTimeSt{}
|
||||
err := gconv.Struct(g.Map{"ServiceDate": nil}, st)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
type T struct {
|
||||
Name string
|
||||
}
|
||||
@ -1054,6 +1022,38 @@ func Test_Struct_NilEmbeddedStructAttribute(t *testing.T) {
|
||||
"name": nil,
|
||||
}, &b)
|
||||
t.Assert(err, nil)
|
||||
g.Dump(b)
|
||||
t.Assert(b.Id, 1)
|
||||
t.Assert(b.Name, "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_JsonParam(t *testing.T) {
|
||||
type A struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
// struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var a = A{}
|
||||
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), &a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a.Id, 1)
|
||||
t.Assert(a.Name, "john")
|
||||
})
|
||||
// *struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var a = &A{}
|
||||
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a.Id, 1)
|
||||
t.Assert(a.Name, "john")
|
||||
})
|
||||
// *struct nil
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var a *A
|
||||
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), &a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a.Id, 1)
|
||||
t.Assert(a.Name, "john")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user