fix: cache value assertion panic if the cache adapter is not in-memory for soft time feature of package gdb; improve converting performance for gconv.Scan (#3351)

This commit is contained in:
John Guo 2024-03-07 11:36:42 +08:00 committed by GitHub
parent cab6b89446
commit 607f079b23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 807 additions and 719 deletions

View File

@ -5,6 +5,8 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gdb provides ORM features for popular relationship databases.
//
// TODO use context.Context as required parameter for all DB operations.
package gdb
import (

View File

@ -11,7 +11,6 @@ import (
"time"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
)
// CacheOption is options for model cache control in query.
@ -67,7 +66,6 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
return
}
var (
ok bool
cacheItem *selectCacheItem
cacheKey = m.makeSelectCacheKey(sql, args...)
cacheObj = m.db.GetCache()
@ -82,12 +80,7 @@ func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args .
}
}()
if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() {
if cacheItem, ok = v.Val().(*selectCacheItem); ok {
// In-memory cache.
return cacheItem.Result, nil
}
// Other cache, it needs conversion.
if err = json.UnmarshalUseNumber(v.Bytes(), &cacheItem); err != nil {
if err = v.Scan(&cacheItem); err != nil {
return nil, err
}
return cacheItem.Result, nil

View File

@ -219,7 +219,10 @@ func (m *softTimeMaintainer) getSoftFieldNameAndType(
intlog.Error(ctx, err)
}
if result != nil {
var cacheItem = result.Val().(getSoftFieldNameAndTypeCacheItem)
var cacheItem getSoftFieldNameAndTypeCacheItem
if err = result.Scan(&cacheItem); err != nil {
return "", ""
}
fieldName = cacheItem.FieldName
fieldType = cacheItem.FieldType
}

View File

@ -11,14 +11,13 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
)
// MapToMap converts any map type variable `params` to another map type variable `pointer`
// using reflect.
// See doMapToMap.
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMap(params, pointer, mapping...)
return Scan(params, pointer, mapping...)
}
// doMapToMap converts any map type variable `params` to another map type variable `pointer`.
@ -32,29 +31,6 @@ func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]str
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the items of original map `params` is type struct.
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(r, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(r, pointer)
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(paramsBytes, pointer)
}
}
}
var (
paramsRv reflect.Value
paramsKind reflect.Kind
@ -92,7 +68,11 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
pointerKind = pointerRv.Kind()
}
if pointerKind != reflect.Map {
return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of *map, but got:%s", pointerKind)
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`destination pointer should be type of *map, but got: %s`,
pointerKind,
)
}
defer func() {
// Catch the panic, especially the reflection operation panics.
@ -119,7 +99,9 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s
mapValue := reflect.New(pointerValueType).Elem()
switch pointerValueKind {
case reflect.Map, reflect.Struct:
if err = doStruct(paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, ""); err != nil {
if err = doStruct(
paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, "",
); err != nil {
return err
}
default:

View File

@ -11,13 +11,12 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
)
// MapToMaps converts any slice type variable `params` to another map slice type variable `pointer`.
// See doMapToMaps.
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMaps(params, pointer, mapping...)
return Scan(params, pointer, mapping...)
}
// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`.
@ -29,29 +28,6 @@ func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]st
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the item of `params` is type struct.
func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(r, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(r, pointer)
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(paramsBytes, pointer)
}
}
}
// Params and its element type check.
var (
paramsRv reflect.Value
@ -68,7 +44,10 @@ func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...m
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Array && paramsKind != reflect.Slice {
return gerror.NewCode(gcode.CodeInvalidParameter, "params should be type of slice, eg: []map/[]*map/[]struct/[]*struct")
return gerror.NewCode(
gcode.CodeInvalidParameter,
"params should be type of slice, example: []map/[]*map/[]struct/[]*struct",
)
}
var (
paramsElem = paramsRv.Type().Elem()
@ -78,8 +57,14 @@ func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...m
paramsElem = paramsElem.Elem()
paramsElemKind = paramsElem.Kind()
}
if paramsElemKind != reflect.Map && paramsElemKind != reflect.Struct && paramsElemKind != reflect.Interface {
return gerror.NewCodef(gcode.CodeInvalidParameter, "params element should be type of map/*map/struct/*struct, but got: %s", paramsElemKind)
if paramsElemKind != reflect.Map &&
paramsElemKind != reflect.Struct &&
paramsElemKind != reflect.Interface {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"params element should be type of map/*map/struct/*struct, but got: %s",
paramsElemKind,
)
}
// Empty slice, no need continue.
if paramsRv.Len() == 0 {

View File

@ -7,91 +7,98 @@
package gconv
import (
"database/sql"
"reflect"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/internal/json"
)
// Scan automatically checks the type of `pointer` and converts `params` to `pointer`. It supports `pointer`
// with type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
// Scan automatically checks the type of `pointer` and converts `params` to `pointer`.
// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
//
// It calls function `doMapToMap` internally if `pointer` is type of *map for converting.
// It calls function `doMapToMaps` internally if `pointer` is type of *[]map/*[]*map for converting.
// It calls function `doStruct` internally if `pointer` is type of *struct/**struct for converting.
// It calls function `doStructs` internally if `pointer` is type of *[]struct/*[]*struct for converting.
func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
var (
pointerType reflect.Type
pointerKind reflect.Kind
pointerValue reflect.Value
)
if v, ok := pointer.(reflect.Value); ok {
pointerValue = v
pointerType = v.Type()
} else {
pointerValue = reflect.ValueOf(pointer)
pointerType = reflect.TypeOf(pointer) // Do not use pointerValue.Type() as pointerValue might be zero.
// TODO change `paramKeyToAttrMap` to `ScanOption` to be more scalable; add `DeepCopy` option for `ScanOption`.
func Scan(srcValue interface{}, dstPointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
if srcValue == nil {
// If `srcValue` is nil, no conversion.
return nil
}
if dstPointer == nil {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`destination pointer should not be nil`,
)
}
if pointerType == nil {
return gerror.NewCode(gcode.CodeInvalidParameter, "parameter pointer should not be nil")
// json converting check.
ok, err := doConvertWithJsonCheck(srcValue, dstPointer)
if err != nil {
return err
}
pointerKind = pointerType.Kind()
if pointerKind != reflect.Ptr {
if pointerValue.CanAddr() {
pointerValue = pointerValue.Addr()
pointerType = pointerValue.Type()
pointerKind = pointerType.Kind()
} else {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"params should be type of pointer, but got type: %v",
pointerType,
)
}
}
// Direct assignment checks!
var (
paramsType reflect.Type
paramsValue reflect.Value
)
if v, ok := params.(reflect.Value); ok {
paramsValue = v
paramsType = paramsValue.Type()
} else {
paramsValue = reflect.ValueOf(params)
paramsType = reflect.TypeOf(params) // Do not use paramsValue.Type() as paramsValue might be zero.
}
// If `params` and `pointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
var (
pointerValueElem = pointerValue.Elem()
)
if pointerValueElem.CanSet() && paramsType == pointerValueElem.Type() {
pointerValueElem.Set(paramsValue)
if ok {
return nil
}
// Converting.
var (
pointerElem = pointerType.Elem()
pointerElemKind = pointerElem.Kind()
keyToAttributeNameMapping map[string]string
dstPointerReflectType reflect.Type
dstPointerReflectValue reflect.Value
)
if v, ok := dstPointer.(reflect.Value); ok {
dstPointerReflectValue = v
dstPointerReflectType = v.Type()
} else {
dstPointerReflectValue = reflect.ValueOf(dstPointer)
// do not use dstPointerReflectValue.Type() as dstPointerReflectValue might be zero.
dstPointerReflectType = reflect.TypeOf(dstPointer)
}
// pointer kind validation.
var dstPointerReflectKind = dstPointerReflectType.Kind()
if dstPointerReflectKind != reflect.Ptr {
if dstPointerReflectValue.CanAddr() {
dstPointerReflectValue = dstPointerReflectValue.Addr()
dstPointerReflectType = dstPointerReflectValue.Type()
dstPointerReflectKind = dstPointerReflectType.Kind()
} else {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`destination pointer should be type of pointer, but got type: %v`,
dstPointerReflectType,
)
}
}
// direct assignment checks!
var srcValueReflectValue reflect.Value
if v, ok := srcValue.(reflect.Value); ok {
srcValueReflectValue = v
} else {
srcValueReflectValue = reflect.ValueOf(srcValue)
}
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
var dstPointerReflectValueElem = dstPointerReflectValue.Elem()
// if `srcValue` and `dstPointer` are the same type, the do directly assignment.
// for performance enhancement purpose.
if ok = doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok {
return nil
}
// do the converting.
var (
dstPointerReflectTypeElem = dstPointerReflectType.Elem()
dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind()
keyToAttributeNameMapping map[string]string
)
if len(paramKeyToAttrMap) > 0 {
keyToAttributeNameMapping = paramKeyToAttrMap[0]
}
switch pointerElemKind {
switch dstPointerReflectTypeElemKind {
case reflect.Map:
return doMapToMap(params, pointer, paramKeyToAttrMap...)
return doMapToMap(srcValue, dstPointer, paramKeyToAttrMap...)
case reflect.Array, reflect.Slice:
var (
sliceElem = pointerElem.Elem()
sliceElem = dstPointerReflectTypeElem.Elem()
sliceElemKind = sliceElem.Kind()
)
for sliceElemKind == reflect.Ptr {
@ -99,432 +106,100 @@ func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[stri
sliceElemKind = sliceElem.Kind()
}
if sliceElemKind == reflect.Map {
return doMapToMaps(params, pointer, paramKeyToAttrMap...)
return doMapToMaps(srcValue, dstPointer, paramKeyToAttrMap...)
}
return doStructs(params, pointer, keyToAttributeNameMapping, "")
return doStructs(srcValue, dstPointer, keyToAttributeNameMapping, "")
default:
return doStruct(params, pointer, keyToAttributeNameMapping, "")
return doStruct(srcValue, dstPointer, keyToAttributeNameMapping, "")
}
}
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
//
// Usage example 1: Normal attribute struct relation:
//
// type EntityUser struct {
// Uid int
// Name string
// }
//
// type EntityUserDetail struct {
// Uid int
// Address string
// }
//
// type EntityUserScores struct {
// Id int
// Uid int
// Score int
// Course string
// }
//
// type Entity struct {
// User *EntityUser
// UserDetail *EntityUserDetail
// UserScores []*EntityUserScores
// }
//
// var users []*Entity
// var userRecords = EntityUser{Uid: 1, Name:"john"}
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
// ScanList(userRecords, &users, "User")
// ScanList(userRecords, &users, "User", "uid")
// ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
// ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
// ScanList(scoresRecords, &users, "UserScores", "User", "uid")
//
// Usage example 2: Embedded attribute struct relation:
//
// type EntityUser struct {
// Uid int
// Name string
// }
//
// type EntityUserDetail struct {
// Uid int
// Address string
// }
//
// type EntityUserScores struct {
// Id int
// Uid int
// Score int
// }
//
// type Entity struct {
// EntityUser
// UserDetail EntityUserDetail
// UserScores []EntityUserScores
// }
//
// var userRecords = EntityUser{Uid: 1, Name:"john"}
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
// ScanList(userRecords, &users)
// ScanList(detailRecords, &users, "UserDetail", "uid")
// ScanList(scoresRecords, &users, "UserScores", "uid")
//
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
// that current result will be bound to.
//
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
// given `relation` parameter.
//
// See the example or unit testing cases for clear understanding for this function.
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
var (
relationAttrName string
relationFields string
)
switch len(relationAttrNameAndFields) {
case 2:
relationAttrName = relationAttrNameAndFields[0]
relationFields = relationAttrNameAndFields[1]
case 1:
relationFields = relationAttrNameAndFields[0]
func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) {
if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() {
return false
}
switch {
// Example:
// UploadFile => UploadFile
// []UploadFile => []UploadFile
// *UploadFile => *UploadFile
// *[]UploadFile => *[]UploadFile
// map => map
// []map => []map
// *[]map => *[]map
case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type():
dstPointerReflectValueElem.Set(srcValueReflectValue)
return true
// Example:
// UploadFile => *UploadFile
// []UploadFile => *[]UploadFile
// map => *map
// []map => *[]map
case dstPointerReflectValueElem.Kind() == reflect.Ptr &&
dstPointerReflectValueElem.Elem().IsValid() &&
dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type():
dstPointerReflectValueElem.Elem().Set(srcValueReflectValue)
return true
// Example:
// *UploadFile => UploadFile
// *[]UploadFile => []UploadFile
// *map => map
// *[]map => []map
case srcValueReflectValue.Kind() == reflect.Ptr &&
srcValueReflectValue.Elem().IsValid() &&
dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type():
dstPointerReflectValueElem.Set(srcValueReflectValue.Elem())
return true
default:
return false
}
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
}
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
func doScanList(
structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
) (err error) {
var (
maps = Maps(structSlice)
)
if len(maps) == 0 {
return nil
}
// Necessary checks for parameters.
if bindToAttrName == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
}
if relationAttrName == "." {
relationAttrName = ""
}
var (
reflectValue = reflect.ValueOf(structSlicePointer)
reflectKind = reflectValue.Kind()
)
if reflectKind == reflect.Interface {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Ptr {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
reflectKind,
)
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
reflectKind,
)
}
length := len(maps)
if length == 0 {
// The pointed slice is not empty.
if reflectValue.Len() > 0 {
// It here checks if it has struct item, which is already initialized.
// It then returns error to warn the developer its empty and no conversion.
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
return sql.ErrNoRows
}
}
// Do nothing for empty struct slice.
return nil
}
var (
arrayValue reflect.Value // Like: []*Entity
arrayItemType reflect.Type // Like: *Entity
reflectType = reflect.TypeOf(structSlicePointer)
)
if reflectValue.Len() > 0 {
arrayValue = reflectValue
} else {
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
}
// Slice element item.
arrayItemType = arrayValue.Index(0).Type()
// Relation variables.
var (
relationDataMap map[string]interface{}
relationFromFieldName string // Eg: relationKV: id:uid -> id
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
)
if len(relationFields) > 0 {
// The relation key string of table field name and attribute name
// can be joined with char '=' or ':'.
array := utils.SplitAndTrim(relationFields, "=")
if len(array) == 1 {
// Compatible with old splitting char ':'.
array = utils.SplitAndTrim(relationFields, ":")
}
if len(array) == 1 {
// The relation names are the same.
array = []string{relationFields, relationFields}
}
if len(array) == 2 {
// Defined table field to relation attribute name.
// Like:
// uid:Uid
// uid:UserId
relationFromFieldName = array[0]
relationBindToFieldName = array[1]
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find possible related table field name "%s" from given relation fields "%s"`,
relationFromFieldName,
relationFields,
)
} else {
relationFromFieldName = key
}
} else {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
)
}
if relationFromFieldName != "" {
// Note that the value might be type of slice.
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
}
if len(relationDataMap) == 0 {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
relationFields,
)
}
}
// Bind to target attribute.
var (
ok bool
bindToAttrValue reflect.Value
bindToAttrKind reflect.Kind
bindToAttrType reflect.Type
bindToAttrField reflect.StructField
)
if arrayItemType.Kind() == reflect.Ptr {
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
} else {
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
}
bindToAttrType = bindToAttrField.Type
bindToAttrKind = bindToAttrType.Kind()
// Bind to relation conditions.
var (
relationFromAttrValue reflect.Value
relationFromAttrField reflect.Value
relationBindToFieldNameChecked bool
)
for i := 0; i < arrayValue.Len(); i++ {
arrayElemValue := arrayValue.Index(i)
// The FieldByName should be called on non-pointer reflect.Value.
if arrayElemValue.Kind() == reflect.Ptr {
// Like: []*Entity
arrayElemValue = arrayElemValue.Elem()
if !arrayElemValue.IsValid() {
// The element is nil, then create one and set it to the slice.
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
// For example:
// reflect.New(itemType.Elem()) => *Entity
// reflect.New(itemType.Elem()).Elem() => Entity
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
arrayValue.Index(i).Set(arrayElemValue.Addr())
}
} else {
// Like: []Entity
}
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
if relationAttrName != "" {
// Attribute value of current slice element.
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
if relationFromAttrValue.Kind() == reflect.Ptr {
relationFromAttrValue = relationFromAttrValue.Elem()
}
} else {
// Current slice element.
relationFromAttrValue = arrayElemValue
}
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
// Check and find possible bind to attribute name.
if relationFields != "" && !relationBindToFieldNameChecked {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if !relationFromAttrField.IsValid() {
var (
fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
Pointer: relationFromAttrValue,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
})
)
if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
relationBindToFieldName,
relationFields,
)
} else {
relationBindToFieldName = key
}
}
relationBindToFieldNameChecked = true
}
switch bindToAttrKind {
case reflect.Array, reflect.Slice:
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
// results := make(Result, 0)
results := make([]interface{}, 0)
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
item := v
results = append(results, item)
// doConvertWithJsonCheck does json converting check.
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
func doConvertWithJsonCheck(srcValue interface{}, dstPointer interface{}) (ok bool, err error) {
switch valueResult := srcValue.(type) {
case []byte:
if json.Valid(valueResult) {
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
if dstPointerReflectType.Kind() == reflect.Ptr {
if dstPointerReflectType.IsNil() {
return false, nil
}
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
return err
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface())
} else if dstPointerReflectType.CanAddr() {
return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface())
}
} else {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`relationKey should not be empty as field "%s" is slice`,
bindToAttrName,
)
return true, json.UnmarshalUseNumber(valueResult, dstPointer)
}
}
case reflect.Ptr:
var element reflect.Value
if bindToAttrValue.IsNil() {
element = reflect.New(bindToAttrType.Elem()).Elem()
} else {
element = bindToAttrValue.Elem()
}
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
v := relationDataMap[String(relationFromAttrField.Interface())]
if v == nil {
// There's no relational data.
continue
case string:
if valueBytes := []byte(valueResult); json.Valid(valueBytes) {
if dstPointerReflectType, ok := dstPointer.(reflect.Value); ok {
if dstPointerReflectType.Kind() == reflect.Ptr {
if dstPointerReflectType.IsNil() {
return false, nil
}
if utils.IsSlice(v) {
if err = Struct(SliceAny(v)[0], element); err != nil {
return err
}
} else {
if err = Struct(v, element); err != nil {
return err
}
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface())
} else if dstPointerReflectType.CanAddr() {
return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface())
}
} else {
if i >= len(maps) {
// There's no relational data.
continue
}
v := maps[i]
if v == nil {
// There's no relational data.
continue
}
if err = Struct(v, element); err != nil {
return err
}
return true, json.UnmarshalUseNumber(valueBytes, dstPointer)
}
bindToAttrValue.Set(element.Addr())
}
case reflect.Struct:
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
if relationDataItem == nil {
// There's no relational data.
continue
}
if utils.IsSlice(relationDataItem) {
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
return err
}
} else {
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
return err
}
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
} else {
if i >= len(maps) {
// There's no relational data.
continue
}
relationDataItem := maps[i]
if relationDataItem == nil {
// There's no relational data.
continue
}
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
return err
}
}
default:
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
default:
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
if v, ok := srcValue.(iInterface); ok {
return doConvertWithJsonCheck(v.Interface(), dstPointer)
}
}
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
return nil
return false, nil
}

View File

@ -0,0 +1,438 @@
// Copyright GoFrame Author(https://goframe.org). 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
import (
"database/sql"
"reflect"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
)
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
//
// Usage example 1: Normal attribute struct relation:
//
// type EntityUser struct {
// Uid int
// Name string
// }
//
// type EntityUserDetail struct {
// Uid int
// Address string
// }
//
// type EntityUserScores struct {
// Id int
// Uid int
// Score int
// Course string
// }
//
// type Entity struct {
// User *EntityUser
// UserDetail *EntityUserDetail
// UserScores []*EntityUserScores
// }
//
// var users []*Entity
// var userRecords = EntityUser{Uid: 1, Name:"john"}
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
// ScanList(userRecords, &users, "User")
// ScanList(userRecords, &users, "User", "uid")
// ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
// ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
// ScanList(scoresRecords, &users, "UserScores", "User", "uid")
//
// Usage example 2: Embedded attribute struct relation:
//
// type EntityUser struct {
// Uid int
// Name string
// }
//
// type EntityUserDetail struct {
// Uid int
// Address string
// }
//
// type EntityUserScores struct {
// Id int
// Uid int
// Score int
// }
//
// type Entity struct {
// EntityUser
// UserDetail EntityUserDetail
// UserScores []EntityUserScores
// }
//
// var userRecords = EntityUser{Uid: 1, Name:"john"}
// var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
// var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
// ScanList(userRecords, &users)
// ScanList(detailRecords, &users, "UserDetail", "uid")
// ScanList(scoresRecords, &users, "UserScores", "uid")
//
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
// that current result will be bound to.
//
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
// given `relation` parameter.
//
// See the example or unit testing cases for clear understanding for this function.
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
var (
relationAttrName string
relationFields string
)
switch len(relationAttrNameAndFields) {
case 2:
relationAttrName = relationAttrNameAndFields[0]
relationFields = relationAttrNameAndFields[1]
case 1:
relationFields = relationAttrNameAndFields[0]
}
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
}
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
func doScanList(
structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
) (err error) {
var (
maps = Maps(structSlice)
)
if len(maps) == 0 {
return nil
}
// Necessary checks for parameters.
if bindToAttrName == "" {
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
}
if relationAttrName == "." {
relationAttrName = ""
}
var (
reflectValue = reflect.ValueOf(structSlicePointer)
reflectKind = reflectValue.Kind()
)
if reflectKind == reflect.Interface {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
}
if reflectKind != reflect.Ptr {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
reflectKind,
)
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
reflectKind,
)
}
length := len(maps)
if length == 0 {
// The pointed slice is not empty.
if reflectValue.Len() > 0 {
// It here checks if it has struct item, which is already initialized.
// It then returns error to warn the developer its empty and no conversion.
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
return sql.ErrNoRows
}
}
// Do nothing for empty struct slice.
return nil
}
var (
arrayValue reflect.Value // Like: []*Entity
arrayItemType reflect.Type // Like: *Entity
reflectType = reflect.TypeOf(structSlicePointer)
)
if reflectValue.Len() > 0 {
arrayValue = reflectValue
} else {
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
}
// Slice element item.
arrayItemType = arrayValue.Index(0).Type()
// Relation variables.
var (
relationDataMap map[string]interface{}
relationFromFieldName string // Eg: relationKV: id:uid -> id
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
)
if len(relationFields) > 0 {
// The relation key string of table field name and attribute name
// can be joined with char '=' or ':'.
array := utils.SplitAndTrim(relationFields, "=")
if len(array) == 1 {
// Compatible with old splitting char ':'.
array = utils.SplitAndTrim(relationFields, ":")
}
if len(array) == 1 {
// The relation names are the same.
array = []string{relationFields, relationFields}
}
if len(array) == 2 {
// Defined table field to relation attribute name.
// Like:
// uid:Uid
// uid:UserId
relationFromFieldName = array[0]
relationBindToFieldName = array[1]
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find possible related table field name "%s" from given relation fields "%s"`,
relationFromFieldName,
relationFields,
)
} else {
relationFromFieldName = key
}
} else {
return gerror.NewCode(
gcode.CodeInvalidParameter,
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
)
}
if relationFromFieldName != "" {
// Note that the value might be type of slice.
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
}
if len(relationDataMap) == 0 {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
relationFields,
)
}
}
// Bind to target attribute.
var (
ok bool
bindToAttrValue reflect.Value
bindToAttrKind reflect.Kind
bindToAttrType reflect.Type
bindToAttrField reflect.StructField
)
if arrayItemType.Kind() == reflect.Ptr {
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
} else {
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
bindToAttrName,
)
}
}
bindToAttrType = bindToAttrField.Type
bindToAttrKind = bindToAttrType.Kind()
// Bind to relation conditions.
var (
relationFromAttrValue reflect.Value
relationFromAttrField reflect.Value
relationBindToFieldNameChecked bool
)
for i := 0; i < arrayValue.Len(); i++ {
arrayElemValue := arrayValue.Index(i)
// The FieldByName should be called on non-pointer reflect.Value.
if arrayElemValue.Kind() == reflect.Ptr {
// Like: []*Entity
arrayElemValue = arrayElemValue.Elem()
if !arrayElemValue.IsValid() {
// The element is nil, then create one and set it to the slice.
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
// For example:
// reflect.New(itemType.Elem()) => *Entity
// reflect.New(itemType.Elem()).Elem() => Entity
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
arrayValue.Index(i).Set(arrayElemValue.Addr())
}
} else {
// Like: []Entity
}
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
if relationAttrName != "" {
// Attribute value of current slice element.
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
if relationFromAttrValue.Kind() == reflect.Ptr {
relationFromAttrValue = relationFromAttrValue.Elem()
}
} else {
// Current slice element.
relationFromAttrValue = arrayElemValue
}
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
// Check and find possible bind to attribute name.
if relationFields != "" && !relationBindToFieldNameChecked {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if !relationFromAttrField.IsValid() {
var (
fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
Pointer: relationFromAttrValue,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
})
)
if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
relationBindToFieldName,
relationFields,
)
} else {
relationBindToFieldName = key
}
}
relationBindToFieldNameChecked = true
}
switch bindToAttrKind {
case reflect.Array, reflect.Slice:
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
// results := make(Result, 0)
results := make([]interface{}, 0)
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
item := v
results = append(results, item)
}
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
return err
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
} else {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`relationKey should not be empty as field "%s" is slice`,
bindToAttrName,
)
}
case reflect.Ptr:
var element reflect.Value
if bindToAttrValue.IsNil() {
element = reflect.New(bindToAttrType.Elem()).Elem()
} else {
element = bindToAttrValue.Elem()
}
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
v := relationDataMap[String(relationFromAttrField.Interface())]
if v == nil {
// There's no relational data.
continue
}
if utils.IsSlice(v) {
if err = Struct(SliceAny(v)[0], element); err != nil {
return err
}
} else {
if err = Struct(v, element); err != nil {
return err
}
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
} else {
if i >= len(maps) {
// There's no relational data.
continue
}
v := maps[i]
if v == nil {
// There's no relational data.
continue
}
if err = Struct(v, element); err != nil {
return err
}
}
bindToAttrValue.Set(element.Addr())
case reflect.Struct:
if len(relationDataMap) > 0 {
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
if relationFromAttrField.IsValid() {
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
if relationDataItem == nil {
// There's no relational data.
continue
}
if utils.IsSlice(relationDataItem) {
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
return err
}
} else {
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
return err
}
}
} else {
// Maybe the attribute does not exist yet.
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
}
} else {
if i >= len(maps) {
// There's no relational data.
continue
}
relationDataItem := maps[i]
if relationDataItem == nil {
// There's no relational data.
continue
}
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
return err
}
}
default:
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
}
}
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
return nil
}

View File

@ -43,50 +43,10 @@ func StructTag(params interface{}, pointer interface{}, priorityTag string) (err
return doStruct(params, pointer, nil, priorityTag)
}
// doStructWithJsonCheck checks if given `params` is JSON, it then uses json.Unmarshal doing the converting.
func doStructWithJsonCheck(params interface{}, pointer interface{}) (err error, ok bool) {
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return nil, false
}
return json.UnmarshalUseNumber(r, rv.Interface()), true
} else if rv.CanAddr() {
return json.UnmarshalUseNumber(r, rv.Addr().Interface()), true
}
} else {
return json.UnmarshalUseNumber(r, pointer), true
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return nil, false
}
return json.UnmarshalUseNumber(paramsBytes, rv.Interface()), true
} else if rv.CanAddr() {
return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()), true
}
} else {
return json.UnmarshalUseNumber(paramsBytes, pointer), true
}
}
default:
// The `params` might be struct that implements interface function Interface, eg: gvar.Var.
if v, ok := params.(iInterface); ok {
return doStructWithJsonCheck(v.Interface(), pointer)
}
}
return nil, false
}
// doStruct is the core internal converting function for any data to struct.
func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string) (err error) {
func doStruct(
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
) (err error) {
if params == nil {
// If `params` is nil, no conversion.
return nil
@ -95,6 +55,15 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
}
// JSON content converting.
ok, err := doConvertWithJsonCheck(params, pointer)
if err != nil {
return err
}
if ok {
return nil
}
defer func() {
// Catch the panic, especially the reflection operation panics.
if exception := recover(); exception != nil {
@ -106,15 +75,6 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
}
}()
// JSON content converting.
err, ok := doStructWithJsonCheck(params, pointer)
if err != nil {
return err
}
if ok {
return nil
}
var (
paramsReflectValue reflect.Value
paramsInterface interface{} // DO NOT use `params` directly as it might be type `reflect.Value`
@ -135,49 +95,35 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
pointerReflectValue = reflect.ValueOf(pointer)
pointerReflectKind = pointerReflectValue.Kind()
if pointerReflectKind != reflect.Ptr {
return gerror.NewCodef(gcode.CodeInvalidParameter, "object pointer should be type of '*struct', but got '%v'", pointerReflectKind)
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"destination 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.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
return gerror.NewCode(
gcode.CodeInvalidParameter,
"destination pointer cannot be nil",
)
}
pointerElemReflectValue = pointerReflectValue.Elem()
}
// custom convert try first
// If `params` and `pointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
if ok = doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok {
return nil
}
// custom convert.
if ok, err = callCustomConverter(paramsReflectValue, pointerReflectValue); ok {
return err
}
// If `params` and `pointer` are the same type, the do directly assignment.
// For performance enhancement purpose.
if pointerElemReflectValue.IsValid() {
switch {
// Eg:
// UploadFile => UploadFile
// *UploadFile => *UploadFile
case pointerElemReflectValue.Type() == paramsReflectValue.Type():
pointerElemReflectValue.Set(paramsReflectValue)
return nil
// Eg:
// UploadFile => *UploadFile
case pointerElemReflectValue.Kind() == reflect.Ptr && pointerElemReflectValue.Elem().IsValid() &&
pointerElemReflectValue.Elem().Type() == paramsReflectValue.Type():
pointerElemReflectValue.Elem().Set(paramsReflectValue)
return nil
// Eg:
// *UploadFile => UploadFile
case paramsReflectValue.Kind() == reflect.Ptr && paramsReflectValue.Elem().IsValid() &&
pointerElemReflectValue.Type() == paramsReflectValue.Elem().Type():
pointerElemReflectValue.Set(paramsReflectValue.Elem())
return nil
}
}
// Normal unmarshalling interfaces checks.
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok {
return err
}
@ -198,7 +144,7 @@ func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[str
// return v.UnmarshalValue(params)
// }
// Note that it's `pointerElemReflectValue` here not `pointerReflectValue`.
if err, ok = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
if ok, err := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok {
return err
}
// Retrieve its element, may be struct at last.
@ -538,7 +484,7 @@ func bindVarToStructAttr(
}
// Common interface check.
if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
if ok, err = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
return err
}
@ -553,24 +499,24 @@ func bindVarToStructAttr(
}
// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks.
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (error, bool) {
func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (bool, error) {
var pointer interface{}
if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() {
reflectValueAddr := reflectValue.Addr()
if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() {
return nil, false
return false, nil
}
// Not a pointer, but can token address, that makes it can be unmarshalled.
pointer = reflectValue.Addr().Interface()
} else {
if reflectValue.IsNil() || !reflectValue.IsValid() {
return nil, false
return false, nil
}
pointer = reflectValue.Interface()
}
// UnmarshalValue.
if v, ok := pointer.(iUnmarshalValue); ok {
return v.UnmarshalValue(value), ok
return ok, v.UnmarshalValue(value)
}
// UnmarshalText.
if v, ok := pointer.(iUnmarshalText); ok {
@ -583,7 +529,7 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
valueBytes = []byte(f.String())
}
if len(valueBytes) > 0 {
return v.UnmarshalText(valueBytes), ok
return ok, v.UnmarshalText(valueBytes)
}
}
// UnmarshalJSON.
@ -606,14 +552,14 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
copy(newValueBytes[1:], valueBytes)
valueBytes = newValueBytes
}
return v.UnmarshalJSON(valueBytes), ok
return ok, v.UnmarshalJSON(valueBytes)
}
}
if v, ok := pointer.(iSet); ok {
v.Set(value)
return nil, ok
return ok, nil
}
return nil, false
return false, nil
}
// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`.
@ -621,7 +567,7 @@ func bindVarToReflectValue(
structFieldValue reflect.Value, value interface{}, paramKeyToAttrMap map[string]string,
) (err error) {
// JSON content converting.
err, ok := doStructWithJsonCheck(value, structFieldValue)
ok, err := doConvertWithJsonCheck(value, structFieldValue)
if err != nil {
return err
}
@ -755,7 +701,7 @@ func bindVarToReflectValue(
if structFieldValue.IsNil() || structFieldValue.IsZero() {
// Nil or empty pointer, it creates a new one.
item := reflect.New(structFieldValue.Type().Elem())
if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
structFieldValue.Set(item)
return err
}

View File

@ -11,7 +11,6 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
)
// Structs converts any slice to given struct slice.
@ -42,18 +41,6 @@ func StructsTag(params interface{}, pointer interface{}, priorityTag string) (er
func doStructs(
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
) (err error) {
if params == nil {
// If `params` is nil, no conversion.
return nil
}
if pointer == nil {
return gerror.NewCode(gcode.CodeInvalidParameter, "object pointer cannot be nil")
}
if doStructsByDirectReflectSet(params, pointer) {
return nil
}
defer func() {
// Catch the panic, especially the reflection operation panics.
if exception := recover(); exception != nil {
@ -64,35 +51,16 @@ func doStructs(
}
}
}()
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(r, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(r, pointer)
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.UnmarshalUseNumber(paramsBytes, rv.Interface())
}
} else {
return json.UnmarshalUseNumber(paramsBytes, pointer)
}
}
}
// Pointer type check.
pointerRv, ok := pointer.(reflect.Value)
if !ok {
pointerRv = reflect.ValueOf(pointer)
if kind := pointerRv.Kind(); kind != reflect.Ptr {
return gerror.NewCodef(gcode.CodeInvalidParameter, "pointer should be type of pointer, but got: %v", kind)
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"pointer should be type of pointer, but got: %v", kind,
)
}
}
// Converting `params` to map slice.
@ -163,17 +131,3 @@ func doStructs(
pointerRv.Elem().Set(reflectElemArray)
return nil
}
// doStructsByDirectReflectSet do the converting directly using reflect Set.
// It returns true if success, or else false.
func doStructsByDirectReflectSet(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
}

View File

@ -7,6 +7,7 @@
package gconv_test
import (
"math/big"
"testing"
"time"
@ -115,6 +116,25 @@ func Test_Issue1227(t *testing.T) {
})
}
// https://github.com/gogf/gf/issues/1607
func Test_Issue1607(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Demo struct {
B Float64
}
rat := &big.Rat{}
rat.SetFloat64(1.5)
var demos = make([]Demo, 1)
err := gconv.Scan([]map[string]interface{}{
{"A": 1, "B": rat},
}, &demos)
t.AssertNil(err)
t.Assert(demos[0].B, 1.5)
})
}
// https://github.com/gogf/gf/issues/1946
func Test_Issue1946(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type B struct {
@ -292,3 +312,17 @@ func Test_Issue2371(t *testing.T) {
t.Assert(s.Time.UTC(), `2022-12-15 08:11:34 +0000 UTC`)
})
}
func Test_Issue2901(t *testing.T) {
type GameApp2 struct {
ForceUpdateTime *time.Time
}
gtest.C(t, func(t *gtest.T) {
src := map[string]interface{}{
"FORCE_UPDATE_TIME": time.Now(),
}
m := GameApp2{}
err := gconv.Scan(src, &m)
t.AssertNil(err)
})
}

View File

@ -641,19 +641,52 @@ func (f *Float64) UnmarshalValue(value interface{}) error {
return nil
}
func Test_Issue1607(t *testing.T) {
func Test_Scan_AutoCreatingPointerElem(t *testing.T) {
type A struct {
Name string
}
gtest.C(t, func(t *gtest.T) {
type Demo struct {
B Float64
var dst A
var src = A{
Name: "john",
}
rat := &big.Rat{}
rat.SetFloat64(1.5)
var demos = make([]Demo, 1)
err := gconv.Scan([]map[string]interface{}{
{"A": 1, "B": rat},
}, &demos)
err := gconv.Scan(src, &dst)
t.AssertNil(err)
t.Assert(demos[0].B, 1.5)
t.Assert(src, dst)
})
gtest.C(t, func(t *gtest.T) {
var dst = &A{
Name: "smith",
}
var src = A{
Name: "john",
}
err := gconv.Scan(src, &dst)
t.AssertNil(err)
t.Assert(src, dst)
})
gtest.C(t, func(t *gtest.T) {
var dst = A{
Name: "smith",
}
var src = &A{
Name: "john",
}
err := gconv.Scan(src, &dst)
t.AssertNil(err)
t.Assert(src, dst)
})
gtest.C(t, func(t *gtest.T) {
var dst *A
var src = &A{
Name: "john",
}
err := gconv.Scan(src, &dst)
t.AssertNil(err)
t.Assert(src, dst)
// Note that the dst points to src.
src.Name = "smith"
t.Assert(src, dst)
})
}

View File

@ -78,17 +78,3 @@ func Test_Time_Slice_Attribute(t *testing.T) {
t.Assert(s.Arr[1], "2021-01-12 12:34:57")
})
}
func Test_Issue2901(t *testing.T) {
type GameApp2 struct {
ForceUpdateTime *time.Time
}
gtest.C(t, func(t *gtest.T) {
src := map[string]interface{}{
"FORCE_UPDATE_TIME": time.Now(),
}
m := GameApp2{}
err := gconv.Scan(src, &m)
t.AssertNil(err)
})
}

View File

@ -10,7 +10,6 @@ package gutil
import (
"reflect"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/util/gconv"
)
@ -18,13 +17,6 @@ const (
dumpIndent = ` `
)
// IsEmpty checks given `value` empty or not.
// It returns false if `value` is: integer(0), bool(false), slice/map(len=0), nil;
// or else returns true.
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}
// Keys retrieves and returns the keys from given map or struct.
func Keys(mapOrStruct interface{}) (keysOrAttrs []string) {
keysOrAttrs = make([]string, 0)

25
util/gutil/gutil_is.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright GoFrame Author(https://goframe.org). 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 gutil
import (
"reflect"
"github.com/gogf/gf/v2/internal/empty"
)
// IsEmpty checks given `value` empty or not.
// It returns false if `value` is: integer(0), bool(false), slice/map(len=0), nil;
// or else returns true.
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}
// IsTypeOf checks and returns whether the type of `value` and `valueInExpectType` equal.
func IsTypeOf(value, valueInExpectType interface{}) bool {
return reflect.TypeOf(value) == reflect.TypeOf(valueInExpectType)
}

View File

@ -0,0 +1,46 @@
// Copyright GoFrame Author(https://goframe.org). 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 gutil_test
import (
"testing"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_IsEmpty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(gutil.IsEmpty(1), false)
})
}
func Test_IsTypeOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(gutil.IsTypeOf(1, 0), true)
t.Assert(gutil.IsTypeOf(1.1, 0.1), true)
t.Assert(gutil.IsTypeOf(1.1, 1), false)
t.Assert(gutil.IsTypeOf(true, false), true)
t.Assert(gutil.IsTypeOf(true, 1), false)
})
gtest.C(t, func(t *gtest.T) {
type A struct {
Name string
}
type B struct {
Name string
}
t.Assert(gutil.IsTypeOf(1, A{}), false)
t.Assert(gutil.IsTypeOf(A{}, B{}), false)
t.Assert(gutil.IsTypeOf(A{Name: "john"}, &A{Name: "john"}), false)
t.Assert(gutil.IsTypeOf(A{Name: "john"}, A{Name: "john"}), true)
t.Assert(gutil.IsTypeOf(A{Name: "john"}, A{}), true)
t.Assert(gutil.IsTypeOf(&A{Name: "john"}, &A{}), true)
t.Assert(gutil.IsTypeOf(&A{Name: "john"}, &B{}), false)
t.Assert(gutil.IsTypeOf(A{Name: "john"}, B{Name: "john"}), false)
})
}

View File

@ -62,12 +62,6 @@ func Test_TryCatch(t *testing.T) {
})
}
func Test_IsEmpty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t.Assert(gutil.IsEmpty(1), false)
})
}
func Test_Throw(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
defer func() {