Merge branch 'gogf:master' into master

This commit is contained in:
mingzaily 2021-11-07 16:40:20 +08:00 committed by GitHub
commit 6f64c26349
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 942 additions and 146 deletions

View File

@ -1776,7 +1776,7 @@ CREATE TABLE %s (
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3)
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5)
})
db.SetDebug(true)
// Result ScanList with struct elements and pointer attributes.
gtest.C(t, func(t *gtest.T) {
var users []Entity

View File

@ -0,0 +1,37 @@
// 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 utils
import "fmt"
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
// Note that the item value may be type of slice.
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
var (
s = ""
m = make(map[string]interface{})
tempMap = make(map[string][]interface{})
hasMultiValues bool
)
for _, item := range list {
if k, ok := item[key]; ok {
s = fmt.Sprintf(`%v`, k)
tempMap[s] = append(tempMap[s], item)
if len(tempMap[s]) > 1 {
hasMultiValues = true
}
}
}
for k, v := range tempMap {
if hasMultiValues {
m[k] = v
} else {
m[k] = v[0]
}
}
return m
}

View File

@ -0,0 +1,26 @@
// 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 utils
// MapPossibleItemByKey tries to find the possible key-value pair for given key ignoring cases and symbols.
//
// Note that this function might be of low performance.
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
if len(data) == 0 {
return
}
if v, ok := data[key]; ok {
return key, v
}
// Loop checking.
for k, v := range data {
if EqualFoldWithoutChars(k, key) {
return k, v
}
}
return "", nil
}

View File

@ -327,14 +327,7 @@ func Split(str, delimiter string) []string {
// and calls Trim to every element of this array. It ignores the elements
// which are empty after Trim.
func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
array := make([]string, 0)
for _, v := range strings.Split(str, delimiter) {
v = Trim(v, characterMask...)
if v != "" {
array = append(array, v)
}
}
return array
return utils.SplitAndTrim(str, delimiter, characterMask...)
}
// Join concatenates the elements of `array` to create a single string. The separator string

View File

@ -7,8 +7,11 @@
package gconv
import (
"database/sql"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/structs"
"github.com/gogf/gf/v2/internal/utils"
"reflect"
)
@ -38,7 +41,18 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
}
pointerKind = pointerType.Kind()
if pointerKind != reflect.Ptr {
return gerror.NewCodef(gcode.CodeInvalidParameter, "params should be type of pointer, but got type: %v", pointerKind)
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 (
@ -94,3 +108,410 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
return doStruct(params, pointer, 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
// ScanList(records, &users, "User")
// ScanList(records, &users, "User", "uid")
// ScanList(records, &users, "UserDetail", "User", "uid:Uid")
// ScanList(records, &users, "UserScores", "User", "uid:Uid")
// ScanList(records, &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 users []*Entity
// ScanList(records, &users)
// ScanList(records, &users, "UserDetail", "uid")
// ScanList(records, &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 filed 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 (
filedMap, _ = structs.FieldMap(structs.FieldMapInput{
Pointer: relationFromAttrValue,
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
)
if key, _ := utils.MapPossibleItemByKey(Map(filedMap), 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

@ -319,3 +319,285 @@ func Test_Scan_SameType_Just_Assign(t *testing.T) {
t.Assert(*m1["int"], *m2["int"])
})
}
func Test_ScanList_Basic(t *testing.T) {
// Struct attribute.
gtest.C(t, func(t *gtest.T) {
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 {
User EntityUser
UserDetail EntityUserDetail
UserScores []EntityUserScores
}
var (
err error
entities []Entity
entityUsers = []EntityUser{
{Uid: 1, Name: "name1"},
{Uid: 2, Name: "name2"},
{Uid: 3, Name: "name3"},
}
userDetails = []EntityUserDetail{
{Uid: 1, Address: "address1"},
{Uid: 2, Address: "address2"},
}
userScores = []EntityUserScores{
{Id: 10, Uid: 1, Score: 100},
{Id: 11, Uid: 1, Score: 60},
{Id: 20, Uid: 2, Score: 99},
}
)
err = gconv.ScanList(entityUsers, &entities, "User")
t.AssertNil(err)
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
t.AssertNil(err)
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
t.AssertNil(err)
t.Assert(len(entities), 3)
t.Assert(entities[0].User, entityUsers[0])
t.Assert(entities[1].User, entityUsers[1])
t.Assert(entities[2].User, entityUsers[2])
t.Assert(entities[0].UserDetail, userDetails[0])
t.Assert(entities[1].UserDetail, userDetails[1])
t.Assert(entities[2].UserDetail, EntityUserDetail{})
t.Assert(len(entities[0].UserScores), 2)
t.Assert(entities[0].UserScores[0], userScores[0])
t.Assert(entities[0].UserScores[1], userScores[1])
t.Assert(len(entities[1].UserScores), 1)
t.Assert(entities[1].UserScores[0], userScores[2])
t.Assert(len(entities[2].UserScores), 0)
})
// Pointer attribute.
gtest.C(t, func(t *gtest.T) {
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 {
User *EntityUser
UserDetail *EntityUserDetail
UserScores []*EntityUserScores
}
var (
err error
entities []*Entity
entityUsers = []*EntityUser{
{Uid: 1, Name: "name1"},
{Uid: 2, Name: "name2"},
{Uid: 3, Name: "name3"},
}
userDetails = []*EntityUserDetail{
{Uid: 1, Address: "address1"},
{Uid: 2, Address: "address2"},
}
userScores = []*EntityUserScores{
{Id: 10, Uid: 1, Score: 100},
{Id: 11, Uid: 1, Score: 60},
{Id: 20, Uid: 2, Score: 99},
}
)
err = gconv.ScanList(entityUsers, &entities, "User")
t.AssertNil(err)
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
t.AssertNil(err)
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
t.AssertNil(err)
t.Assert(len(entities), 3)
t.Assert(entities[0].User, entityUsers[0])
t.Assert(entities[1].User, entityUsers[1])
t.Assert(entities[2].User, entityUsers[2])
t.Assert(entities[0].UserDetail, userDetails[0])
t.Assert(entities[1].UserDetail, userDetails[1])
t.Assert(entities[2].UserDetail, nil)
t.Assert(len(entities[0].UserScores), 2)
t.Assert(entities[0].UserScores[0], userScores[0])
t.Assert(entities[0].UserScores[1], userScores[1])
t.Assert(len(entities[1].UserScores), 1)
t.Assert(entities[1].UserScores[0], userScores[2])
t.Assert(len(entities[2].UserScores), 0)
})
}
func Test_ScanList_Embedded(t *testing.T) {
// Struct attribute.
gtest.C(t, func(t *gtest.T) {
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 (
err error
entities []Entity
entityUsers = []EntityUser{
{Uid: 1, Name: "name1"},
{Uid: 2, Name: "name2"},
{Uid: 3, Name: "name3"},
}
userDetails = []EntityUserDetail{
{Uid: 1, Address: "address1"},
{Uid: 2, Address: "address2"},
}
userScores = []EntityUserScores{
{Id: 10, Uid: 1, Score: 100},
{Id: 11, Uid: 1, Score: 60},
{Id: 20, Uid: 2, Score: 99},
}
)
err = gconv.Scan(entityUsers, &entities)
t.AssertNil(err)
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
t.AssertNil(err)
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
t.AssertNil(err)
t.Assert(len(entities), 3)
t.Assert(entities[0].EntityUser, entityUsers[0])
t.Assert(entities[1].EntityUser, entityUsers[1])
t.Assert(entities[2].EntityUser, entityUsers[2])
t.Assert(entities[0].UserDetail, userDetails[0])
t.Assert(entities[1].UserDetail, userDetails[1])
t.Assert(entities[2].UserDetail, EntityUserDetail{})
t.Assert(len(entities[0].UserScores), 2)
t.Assert(entities[0].UserScores[0], userScores[0])
t.Assert(entities[0].UserScores[1], userScores[1])
t.Assert(len(entities[1].UserScores), 1)
t.Assert(entities[1].UserScores[0], userScores[2])
t.Assert(len(entities[2].UserScores), 0)
})
// Pointer attribute.
gtest.C(t, func(t *gtest.T) {
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 (
err error
entities []Entity
entityUsers = []EntityUser{
{Uid: 1, Name: "name1"},
{Uid: 2, Name: "name2"},
{Uid: 3, Name: "name3"},
}
userDetails = []EntityUserDetail{
{Uid: 1, Address: "address1"},
{Uid: 2, Address: "address2"},
}
userScores = []EntityUserScores{
{Id: 10, Uid: 1, Score: 100},
{Id: 11, Uid: 1, Score: 60},
{Id: 20, Uid: 2, Score: 99},
}
)
err = gconv.Scan(entityUsers, &entities)
t.AssertNil(err)
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
t.AssertNil(err)
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
t.AssertNil(err)
t.Assert(len(entities), 3)
t.Assert(entities[0].EntityUser, entityUsers[0])
t.Assert(entities[1].EntityUser, entityUsers[1])
t.Assert(entities[2].EntityUser, entityUsers[2])
t.Assert(entities[0].UserDetail, userDetails[0])
t.Assert(entities[1].UserDetail, userDetails[1])
t.Assert(entities[2].UserDetail, nil)
t.Assert(len(entities[0].UserScores), 2)
t.Assert(entities[0].UserScores[0], userScores[0])
t.Assert(entities[0].UserScores[1], userScores[1])
t.Assert(len(entities[1].UserScores), 1)
t.Assert(entities[1].UserScores[0], userScores[2])
t.Assert(len(entities[2].UserScores), 0)
})
}

View File

@ -15,6 +15,16 @@ import (
"strings"
)
// iString is used for type assert api for String().
type iString interface {
String() string
}
// iMarshalJSON is the interface for custom Json marshaling.
type iMarshalJSON interface {
MarshalJSON() ([]byte, error)
}
// ExportOption specifies the behavior of function Export.
type ExportOption struct {
WithoutType bool // WithoutType specifies exported content has no type information.
@ -151,7 +161,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
"%s%v:%s",
newIndent,
mapKeyStr,
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
))
} else {
buffer.WriteString(fmt.Sprintf(
@ -159,7 +169,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
newIndent,
mapKey.Type().String(),
mapKeyStr,
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
))
}
doExport(reflectValue.MapIndex(mapKey).Interface(), newIndent, buffer, option)
@ -173,10 +183,31 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
if len(structFields) == 0 {
if option.WithoutType {
buffer.WriteString("{}")
var (
structContentStr = ""
attributeCountStr = "0"
)
if v, ok := value.(iString); ok {
structContentStr = v.String()
} else if v, ok := value.(iMarshalJSON); ok {
b, _ := v.MarshalJSON()
structContentStr = string(b)
}
if structContentStr == "" {
structContentStr = "{}"
} else {
buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName))
structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr))
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
}
if option.WithoutType {
buffer.WriteString(structContentStr)
} else {
buffer.WriteString(fmt.Sprintf(
"%s(%s) %s",
reflectTypeName,
attributeCountStr,
structContentStr,
))
}
return
}
@ -202,7 +233,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
"%s%s:%s",
newIndent,
field.Name(),
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
))
doExport(field.Value.Interface(), newIndent, buffer, option)
buffer.WriteString(",\n")

View File

@ -7,6 +7,7 @@
package gutil
import (
"github.com/gogf/gf/v2/internal/utils"
"reflect"
)
@ -130,3 +131,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
}
return values
}
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
// Note that the item value may be type of slice.
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
return utils.ListToMapByKey(list, key)
}

View File

@ -67,19 +67,7 @@ func MapMergeCopy(src ...map[string]interface{}) (copy map[string]interface{}) {
//
// Note that this function might be of low performance.
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
if len(data) == 0 {
return
}
if v, ok := data[key]; ok {
return key, v
}
// Loop checking.
for k, v := range data {
if utils.EqualFoldWithoutChars(k, key) {
return k, v
}
}
return "", nil
return utils.MapPossibleItemByKey(data, key)
}
// MapContainsPossibleKey checks if the given `key` is contained in given map `data`.

View File

@ -0,0 +1,128 @@
// 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 (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gmeta"
"testing"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Dump(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
}
type SetSpecInfo struct {
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
}
type CreateResourceReq struct {
CommonReq
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
Name string
CreatedAt *gtime.Time
SetMap map[string]*SetSpecInfo
SetSlice []SetSpecInfo
Handler ghttp.HandlerFunc
internal string
}
req := &CreateResourceReq{
CommonReq: CommonReq{
AppId: 12345678,
ResourceId: "tdchqy-xxx",
},
Name: "john",
CreatedAt: gtime.Now(),
SetMap: map[string]*SetSpecInfo{
"test1": {
StorageType: "ssd",
Shards: 2,
Params: []string{"a", "b", "c"},
},
"test2": {
StorageType: "hssd",
Shards: 10,
Params: []string{},
},
},
SetSlice: []SetSpecInfo{
{
StorageType: "hssd",
Shards: 10,
Params: []string{"h"},
},
},
}
gtest.C(t, func(t *gtest.T) {
gutil.Dump(map[int]int{
100: 100,
})
gutil.Dump(req)
})
}
func TestDumpWithType(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
}
type SetSpecInfo struct {
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
}
type CreateResourceReq struct {
CommonReq
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
Name string
CreatedAt *gtime.Time
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
Handler ghttp.HandlerFunc
internal string
}
req := &CreateResourceReq{
CommonReq: CommonReq{
AppId: 12345678,
ResourceId: "tdchqy-xxx",
},
Name: "john",
CreatedAt: gtime.Now(),
SetMap: map[string]*SetSpecInfo{
"test1": {
StorageType: "ssd",
Shards: 2,
Params: []string{"a", "b", "c"},
},
"test2": {
StorageType: "hssd",
Shards: 10,
Params: []string{},
},
},
SetSlice: []SetSpecInfo{
{
StorageType: "hssd",
Shards: 10,
Params: []string{"h"},
},
},
}
gtest.C(t, func(t *gtest.T) {
gutil.DumpWithType(map[int]int{
100: 100,
})
gutil.DumpWithType(req)
gutil.DumpWithType([][]byte{[]byte("hello")})
})
}

View File

@ -8,129 +8,12 @@ package gutil_test
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gmeta"
"testing"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gutil"
)
func Test_Dump(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
}
type SetSpecInfo struct {
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
}
type CreateResourceReq struct {
CommonReq
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
Name string `des:"实例名称"`
Product string `des:"业务类型"`
Region string `v:"required" des:"区域"`
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
Handler ghttp.HandlerFunc
internal string
}
req := &CreateResourceReq{
CommonReq: CommonReq{
AppId: 12345678,
ResourceId: "tdchqy-xxx",
},
Name: "john",
Product: "goframe",
Region: "cd",
SetMap: map[string]*SetSpecInfo{
"test1": {
StorageType: "ssd",
Shards: 2,
Params: []string{"a", "b", "c"},
},
"test2": {
StorageType: "hssd",
Shards: 10,
Params: []string{},
},
},
SetSlice: []SetSpecInfo{
{
StorageType: "hssd",
Shards: 10,
Params: []string{"h"},
},
},
}
gtest.C(t, func(t *gtest.T) {
gutil.Dump(map[int]int{
100: 100,
})
gutil.Dump(req)
})
}
func TestDumpWithType(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
}
type SetSpecInfo struct {
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
}
type CreateResourceReq struct {
CommonReq
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
Name string `des:"实例名称"`
Product string `des:"业务类型"`
Region string `v:"required" des:"区域"`
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
Handler ghttp.HandlerFunc
internal string
}
req := &CreateResourceReq{
CommonReq: CommonReq{
AppId: 12345678,
ResourceId: "tdchqy-xxx",
},
Name: "john",
Product: "goframe",
Region: "cd",
SetMap: map[string]*SetSpecInfo{
"test1": {
StorageType: "ssd",
Shards: 2,
Params: []string{"a", "b", "c"},
},
"test2": {
StorageType: "hssd",
Shards: 10,
Params: []string{},
},
},
SetSlice: []SetSpecInfo{
{
StorageType: "hssd",
Shards: 10,
Params: []string{"h"},
},
},
}
gtest.C(t, func(t *gtest.T) {
gutil.DumpWithType(map[int]int{
100: 100,
})
gutil.DumpWithType(req)
gutil.DumpWithType([][]byte{[]byte("hello")})
})
}
func Test_Try(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := `gutil Try test`