From 57a82ebcc0606d79de59473a0ecb53a475b06ed8 Mon Sep 17 00:00:00 2001 From: jianchenma Date: Fri, 4 Dec 2020 18:17:11 +0800 Subject: [PATCH] improve gconv.Struct --- database/gdb/gdb_type_result.go | 17 +-- util/gconv/gconv_struct.go | 121 ++++++++---------- .../gconv_z_unit_struct_interface_test.go | 103 +++++++++++++++ util/gconv/gconv_z_unit_struct_test.go | 66 +++++----- 4 files changed, 196 insertions(+), 111 deletions(-) create mode 100644 util/gconv/gconv_z_unit_struct_interface_test.go diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index 62f4ee4dc..9184daf0d 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -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 diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index b9b0522d8..5f618e4b9 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -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 is **User, then 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 (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 is **User, then 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) diff --git a/util/gconv/gconv_z_unit_struct_interface_test.go b/util/gconv/gconv_z_unit_struct_interface_test.go new file mode 100644 index 000000000..2794ef031 --- /dev/null +++ b/util/gconv/gconv_z_unit_struct_interface_test.go @@ -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) + }) +} diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index 8141fa26c..1932b13ce 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -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") }) }