mirror of
https://gitee.com/johng/gf.git
synced 2024-11-29 10:47:38 +08:00
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:
parent
cab6b89446
commit
607f079b23
@ -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 (
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
438
util/gconv/gconv_scan_list.go
Normal file
438
util/gconv/gconv_scan_list.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
25
util/gutil/gutil_is.go
Normal 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)
|
||||
}
|
46
util/gutil/gutil_z_unit_is_test.go
Normal file
46
util/gutil/gutil_z_unit_is_test.go
Normal 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)
|
||||
})
|
||||
}
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user