diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD index 4f959f68692..5a79a607ec7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.MD +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -1,16 +1,16 @@ **Please ensure you adhere to every item in this list.** + The PR title is formatted as follows: `[optional scope]: ` For example, `fix(os/gtime): fix time zone issue` + `` is mandatory and can be one of `fix`, `feat`, `build`, `ci`, `docs`, `style`, `refactor`, `perf`, `test`, `chore` - + fix: Used when a bug has been fixed. - + feat: Used when a new feature has been added. - + build: Used for modifications to the project build system, such as changes to dependencies, external interfaces, or upgrading Node version. - + ci: Used for modifications to continuous integration processes, such as changes to Travis, Jenkins workflow configurations. - + docs: Used for modifications to documentation, such as changes to README files, API documentation, etc. - + style: Used for changes to code style, such as adjustments to indentation, spaces, blank lines, etc. - + refactor: Used for code refactoring, such as changes to code structure, variable names, function names, without altering functionality. - + perf: Used for performance optimization, such as improving code performance, reducing memory usage, etc. - + test: Used for modifications to test cases, such as adding, deleting, or modifying test cases for code. - + chore: Used for modifications to non-business-related code, such as changes to build processes or tool configurations. + + `fix`: Used when a bug has been fixed. + + `feat`: Used when a new feature has been added. + + `build`: Used for modifications to the project build system, such as changes to dependencies, external interfaces, or upgrading Node version. + + `ci`: Used for modifications to continuous integration processes, such as changes to Travis, Jenkins workflow configurations. + + `docs`: Used for modifications to documentation, such as changes to README files, API documentation, etc. + + `style`: Used for changes to code style, such as adjustments to indentation, spaces, blank lines, etc. + + `refactor`: Used for code refactoring, such as changes to code structure, variable names, function names, without altering functionality. + + `perf`: Used for performance optimization, such as improving code performance, reducing memory usage, etc. + + `test`: Used for modifications to test cases, such as adding, deleting, or modifying test cases for code. + + `chore`: Used for modifications to non-business-related code, such as changes to build processes or tool configurations. + After ``, specify the affected package name or scope in parentheses, for example, `(os/gtime)`. + The part after the colon uses the verb tense + phrase that completes the blank in + Lowercase verb after the colon diff --git a/Makefile b/Makefile index 85d4f529958..03f69483951 100644 --- a/Makefile +++ b/Makefile @@ -34,8 +34,8 @@ version: subup: @set -e; \ echo "Updating submodules..."; \ - cd examples && git pull origin main; \ - cd ..; + git submodule init;\ + git submodule update; # update and commit submodules .PHONY: subsync diff --git a/cmd/gf/internal/cmd/gendao/gendao.go b/cmd/gf/internal/cmd/gendao/gendao.go index ff190a401cf..5c38c5c69a8 100644 --- a/cmd/gf/internal/cmd/gendao/gendao.go +++ b/cmd/gf/internal/cmd/gendao/gendao.go @@ -36,7 +36,7 @@ type ( Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"` Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"` TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"` - ShardingPattern []string `name:"ShardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"` + ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"` Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"` Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"` RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"` diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go index 3f7781421c6..07cd9af69b6 100644 --- a/container/gvar/gvar.go +++ b/container/gvar/gvar.go @@ -8,14 +8,8 @@ package gvar import ( - "time" - "github.com/gogf/gf/v2/container/gtype" - "github.com/gogf/gf/v2/internal/deepcopy" "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gtime" - "github.com/gogf/gf/v2/util/gconv" - "github.com/gogf/gf/v2/util/gutil" ) // Var is an universal variable type implementer. @@ -39,144 +33,8 @@ func New(value interface{}, safe ...bool) *Var { } } -// Copy does a deep copy of current Var and returns a pointer to this Var. -func (v *Var) Copy() *Var { - return New(gutil.Copy(v.Val()), v.safe) -} - -// Clone does a shallow copy of current Var and returns a pointer to this Var. -func (v *Var) Clone() *Var { - return New(v.Val(), v.safe) -} - -// Set sets `value` to `v`, and returns the old value. -func (v *Var) Set(value interface{}) (old interface{}) { - if v.safe { - if t, ok := v.value.(*gtype.Interface); ok { - old = t.Set(value) - return - } - } - old = v.value - v.value = value - return -} - -// Val returns the current value of `v`. -func (v *Var) Val() interface{} { - if v == nil { - return nil - } - if v.safe { - if t, ok := v.value.(*gtype.Interface); ok { - return t.Val() - } - } - return v.value -} - -// Interface is alias of Val. -func (v *Var) Interface() interface{} { - return v.Val() -} - -// Bytes converts and returns `v` as []byte. -func (v *Var) Bytes() []byte { - return gconv.Bytes(v.Val()) -} - -// String converts and returns `v` as string. -func (v *Var) String() string { - return gconv.String(v.Val()) -} - -// Bool converts and returns `v` as bool. -func (v *Var) Bool() bool { - return gconv.Bool(v.Val()) -} - -// Int converts and returns `v` as int. -func (v *Var) Int() int { - return gconv.Int(v.Val()) -} - -// Int8 converts and returns `v` as int8. -func (v *Var) Int8() int8 { - return gconv.Int8(v.Val()) -} - -// Int16 converts and returns `v` as int16. -func (v *Var) Int16() int16 { - return gconv.Int16(v.Val()) -} - -// Int32 converts and returns `v` as int32. -func (v *Var) Int32() int32 { - return gconv.Int32(v.Val()) -} - -// Int64 converts and returns `v` as int64. -func (v *Var) Int64() int64 { - return gconv.Int64(v.Val()) -} - -// Uint converts and returns `v` as uint. -func (v *Var) Uint() uint { - return gconv.Uint(v.Val()) -} - -// Uint8 converts and returns `v` as uint8. -func (v *Var) Uint8() uint8 { - return gconv.Uint8(v.Val()) -} - -// Uint16 converts and returns `v` as uint16. -func (v *Var) Uint16() uint16 { - return gconv.Uint16(v.Val()) -} - -// Uint32 converts and returns `v` as uint32. -func (v *Var) Uint32() uint32 { - return gconv.Uint32(v.Val()) -} - -// Uint64 converts and returns `v` as uint64. -func (v *Var) Uint64() uint64 { - return gconv.Uint64(v.Val()) -} - -// Float32 converts and returns `v` as float32. -func (v *Var) Float32() float32 { - return gconv.Float32(v.Val()) -} - -// Float64 converts and returns `v` as float64. -func (v *Var) Float64() float64 { - return gconv.Float64(v.Val()) -} - -// Time converts and returns `v` as time.Time. -// The parameter `format` specifies the format of the time string using gtime, -// eg: Y-m-d H:i:s. -func (v *Var) Time(format ...string) time.Time { - return gconv.Time(v.Val(), format...) -} - -// Duration converts and returns `v` as time.Duration. -// If value of `v` is string, then it uses time.ParseDuration for conversion. -func (v *Var) Duration() time.Duration { - return gconv.Duration(v.Val()) -} - -// GTime converts and returns `v` as *gtime.Time. -// The parameter `format` specifies the format of the time string using gtime, -// eg: Y-m-d H:i:s. -func (v *Var) GTime(format ...string) *gtime.Time { - return gconv.GTime(v.Val(), format...) -} - // MarshalJSON implements the interface MarshalJSON for json.Marshal. -func (v Var) MarshalJSON() ([]byte, error) { +func (v *Var) MarshalJSON() ([]byte, error) { return json.Marshal(v.Val()) } @@ -195,11 +53,3 @@ func (v *Var) UnmarshalValue(value interface{}) error { v.Set(value) return nil } - -// DeepCopy implements interface for deep copy of current type. -func (v *Var) DeepCopy() interface{} { - if v == nil { - return nil - } - return New(deepcopy.Copy(v.Val()), v.safe) -} diff --git a/container/gvar/gvar_basic.go b/container/gvar/gvar_basic.go new file mode 100644 index 00000000000..b57b235578d --- /dev/null +++ b/container/gvar/gvar_basic.go @@ -0,0 +1,105 @@ +// 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 gvar + +import ( + "github.com/gogf/gf/v2/container/gtype" + "github.com/gogf/gf/v2/util/gconv" +) + +// Val returns the current value of `v`. +func (v *Var) Val() any { + if v == nil { + return nil + } + if v.safe { + if t, ok := v.value.(*gtype.Interface); ok { + return t.Val() + } + } + return v.value +} + +// Interface is alias of Val. +func (v *Var) Interface() any { + return v.Val() +} + +// Bytes converts and returns `v` as []byte. +func (v *Var) Bytes() []byte { + return gconv.Bytes(v.Val()) +} + +// String converts and returns `v` as string. +func (v *Var) String() string { + return gconv.String(v.Val()) +} + +// Bool converts and returns `v` as bool. +func (v *Var) Bool() bool { + return gconv.Bool(v.Val()) +} + +// Int converts and returns `v` as int. +func (v *Var) Int() int { + return gconv.Int(v.Val()) +} + +// Int8 converts and returns `v` as int8. +func (v *Var) Int8() int8 { + return gconv.Int8(v.Val()) +} + +// Int16 converts and returns `v` as int16. +func (v *Var) Int16() int16 { + return gconv.Int16(v.Val()) +} + +// Int32 converts and returns `v` as int32. +func (v *Var) Int32() int32 { + return gconv.Int32(v.Val()) +} + +// Int64 converts and returns `v` as int64. +func (v *Var) Int64() int64 { + return gconv.Int64(v.Val()) +} + +// Uint converts and returns `v` as uint. +func (v *Var) Uint() uint { + return gconv.Uint(v.Val()) +} + +// Uint8 converts and returns `v` as uint8. +func (v *Var) Uint8() uint8 { + return gconv.Uint8(v.Val()) +} + +// Uint16 converts and returns `v` as uint16. +func (v *Var) Uint16() uint16 { + return gconv.Uint16(v.Val()) +} + +// Uint32 converts and returns `v` as uint32. +func (v *Var) Uint32() uint32 { + return gconv.Uint32(v.Val()) +} + +// Uint64 converts and returns `v` as uint64. +func (v *Var) Uint64() uint64 { + return gconv.Uint64(v.Val()) +} + +// Float32 converts and returns `v` as float32. +func (v *Var) Float32() float32 { + return gconv.Float32(v.Val()) +} + +// Float64 converts and returns `v` as float64. +func (v *Var) Float64() float64 { + return gconv.Float64(v.Val()) +} diff --git a/container/gvar/gvar_copy.go b/container/gvar/gvar_copy.go new file mode 100644 index 00000000000..a85d3748288 --- /dev/null +++ b/container/gvar/gvar_copy.go @@ -0,0 +1,30 @@ +// 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 gvar + +import ( + "github.com/gogf/gf/v2/internal/deepcopy" + "github.com/gogf/gf/v2/util/gutil" +) + +// Copy does a deep copy of current Var and returns a pointer to this Var. +func (v *Var) Copy() *Var { + return New(gutil.Copy(v.Val()), v.safe) +} + +// Clone does a shallow copy of current Var and returns a pointer to this Var. +func (v *Var) Clone() *Var { + return New(v.Val(), v.safe) +} + +// DeepCopy implements interface for deep copy of current type. +func (v *Var) DeepCopy() interface{} { + if v == nil { + return nil + } + return New(deepcopy.Copy(v.Val()), v.safe) +} diff --git a/container/gvar/gvar_scan.go b/container/gvar/gvar_scan.go index 469005b5034..5c38bfbfe6d 100644 --- a/container/gvar/gvar_scan.go +++ b/container/gvar/gvar_scan.go @@ -10,8 +10,7 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) -// 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 value of Var to `pointer`. // // See gconv.Scan. func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error { diff --git a/container/gvar/gvar_set.go b/container/gvar/gvar_set.go new file mode 100644 index 00000000000..a00b2a627d4 --- /dev/null +++ b/container/gvar/gvar_set.go @@ -0,0 +1,24 @@ +// 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 gvar + +import ( + "github.com/gogf/gf/v2/container/gtype" +) + +// Set sets `value` to `v`, and returns the old value. +func (v *Var) Set(value interface{}) (old interface{}) { + if v.safe { + if t, ok := v.value.(*gtype.Interface); ok { + old = t.Set(value) + return + } + } + old = v.value + v.value = value + return +} diff --git a/container/gvar/gvar_time.go b/container/gvar/gvar_time.go new file mode 100644 index 00000000000..2f47ad0815a --- /dev/null +++ b/container/gvar/gvar_time.go @@ -0,0 +1,34 @@ +// 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 gvar + +import ( + "time" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +// Time converts and returns `v` as time.Time. +// The parameter `format` specifies the format of the time string using gtime, +// eg: Y-m-d H:i:s. +func (v *Var) Time(format ...string) time.Time { + return gconv.Time(v.Val(), format...) +} + +// Duration converts and returns `v` as time.Duration. +// If value of `v` is string, then it uses time.ParseDuration for conversion. +func (v *Var) Duration() time.Duration { + return gconv.Duration(v.Val()) +} + +// GTime converts and returns `v` as *gtime.Time. +// The parameter `format` specifies the format of the time string using gtime, +// eg: Y-m-d H:i:s. +func (v *Var) GTime(format ...string) *gtime.Time { + return gconv.GTime(v.Val(), format...) +} diff --git a/contrib/drivers/mysql/mysql_z_unit_feature_model_struct_test.go b/contrib/drivers/mysql/mysql_z_unit_feature_model_struct_test.go index 7d9902c799b..1463c4080db 100644 --- a/contrib/drivers/mysql/mysql_z_unit_feature_model_struct_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_feature_model_struct_test.go @@ -17,7 +17,6 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/test/gtest" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" ) @@ -481,117 +480,3 @@ func Test_Scan_AutoFilteringByStructAttributes(t *testing.T) { t.Assert(users[0].Id, 1) }) } - -func Test_Scan_JsonAttributes(t *testing.T) { - type GiftImage struct { - Uid string `json:"uid"` - Url string `json:"url"` - Status string `json:"status"` - Name string `json:"name"` - } - - type GiftComment struct { - Name string `json:"name"` - Field string `json:"field"` - Required bool `json:"required"` - } - - type Prop struct { - Name string `json:"name"` - Values []string `json:"values"` - } - - type Sku struct { - GiftId int64 `json:"gift_id"` - Name string `json:"name"` - ScorePrice int `json:"score_price"` - MarketPrice int `json:"market_price"` - CostPrice int `json:"cost_price"` - Stock int `json:"stock"` - } - - type Covers struct { - List []GiftImage `json:"list"` - } - - type GiftEntity struct { - Id int64 `json:"id"` - StoreId int64 `json:"store_id"` - GiftType int `json:"gift_type"` - GiftName string `json:"gift_name"` - Description string `json:"description"` - Covers Covers `json:"covers"` - Cover string `json:"cover"` - GiftCategoryId []int64 `json:"gift_category_id"` - HasProps bool `json:"has_props"` - OutSn string `json:"out_sn"` - IsLimitSell bool `json:"is_limit_sell"` - LimitSellType int `json:"limit_sell_type"` - LimitSellCycle string `json:"limit_sell_cycle"` - LimitSellCycleCount int `json:"limit_sell_cycle_count"` - LimitSellCustom bool `json:"limit_sell_custom"` // 只允许特定会员兑换 - LimitCustomerTags []int64 `json:"limit_customer_tags"` // 允许兑换的成员 - ScorePrice int `json:"score_price"` - MarketPrice float64 `json:"market_price"` - CostPrice int `json:"cost_price"` - Stock int `json:"stock"` - Props []Prop `json:"props"` - Skus []Sku `json:"skus"` - ExpressType []string `json:"express_type"` - Comments []GiftComment `json:"comments"` - Content string `json:"content"` - AtLeastRechargeCount int `json:"at_least_recharge_count"` - Status int `json:"status"` - } - - type User struct { - Id int - Passport string - } - - table := "jfy_gift" - array := gstr.SplitAndTrim(gtest.DataContent(`issue1380.sql`), ";") - for _, v := range array { - if _, err := db.Exec(ctx, v); err != nil { - gtest.Error(err) - } - } - defer dropTable(table) - - gtest.C(t, func(t *gtest.T) { - var ( - entity = new(GiftEntity) - err = db.Model(table).Where("id", 17).Scan(entity) - ) - t.AssertNil(err) - t.Assert(len(entity.Skus), 2) - - t.Assert(entity.Skus[0].Name, "red") - t.Assert(entity.Skus[0].Stock, 10) - t.Assert(entity.Skus[0].GiftId, 1) - t.Assert(entity.Skus[0].CostPrice, 80) - t.Assert(entity.Skus[0].ScorePrice, 188) - t.Assert(entity.Skus[0].MarketPrice, 388) - - t.Assert(entity.Skus[1].Name, "blue") - t.Assert(entity.Skus[1].Stock, 100) - t.Assert(entity.Skus[1].GiftId, 2) - t.Assert(entity.Skus[1].CostPrice, 81) - t.Assert(entity.Skus[1].ScorePrice, 200) - t.Assert(entity.Skus[1].MarketPrice, 288) - - t.Assert(entity.Id, 17) - t.Assert(entity.StoreId, 100004) - t.Assert(entity.GiftType, 1) - t.Assert(entity.GiftName, "GIFT") - t.Assert(entity.Description, "支持个性定制的父亲节老师长辈的专属礼物") - t.Assert(len(entity.Covers.List), 3) - t.Assert(entity.OutSn, "259402") - t.Assert(entity.LimitCustomerTags, "[]") - t.Assert(entity.ScorePrice, 10) - t.Assert(len(entity.Props), 1) - t.Assert(len(entity.Comments), 2) - t.Assert(entity.Status, 99) - t.Assert(entity.Content, `

礼品详情

`) - }) -} diff --git a/contrib/drivers/mysql/mysql_z_unit_issue_test.go b/contrib/drivers/mysql/mysql_z_unit_issue_test.go index af4e43b29e1..7f7f0a85401 100644 --- a/contrib/drivers/mysql/mysql_z_unit_issue_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_issue_test.go @@ -8,6 +8,7 @@ package mysql_test import ( "context" + "encoding/json" "fmt" "sync" "testing" @@ -24,6 +25,121 @@ import ( "github.com/gogf/gf/v2/util/guid" ) +// https://github.com/gogf/gf/issues/1380 +func Test_Issue1380(t *testing.T) { + type GiftImage struct { + Uid string `json:"uid"` + Url string `json:"url"` + Status string `json:"status"` + Name string `json:"name"` + } + + type GiftComment struct { + Name string `json:"name"` + Field string `json:"field"` + Required bool `json:"required"` + } + + type Prop struct { + Name string `json:"name"` + Values []string `json:"values"` + } + + type Sku struct { + GiftId int64 `json:"gift_id"` + Name string `json:"name"` + ScorePrice int `json:"score_price"` + MarketPrice int `json:"market_price"` + CostPrice int `json:"cost_price"` + Stock int `json:"stock"` + } + + type Covers struct { + List []GiftImage `json:"list"` + } + + type GiftEntity struct { + Id int64 `json:"id"` + StoreId int64 `json:"store_id"` + GiftType int `json:"gift_type"` + GiftName string `json:"gift_name"` + Description string `json:"description"` + Covers Covers `json:"covers"` + Cover string `json:"cover"` + GiftCategoryId []int64 `json:"gift_category_id"` + HasProps bool `json:"has_props"` + OutSn string `json:"out_sn"` + IsLimitSell bool `json:"is_limit_sell"` + LimitSellType int `json:"limit_sell_type"` + LimitSellCycle string `json:"limit_sell_cycle"` + LimitSellCycleCount int `json:"limit_sell_cycle_count"` + LimitSellCustom bool `json:"limit_sell_custom"` // 只允许特定会员兑换 + LimitCustomerTags []int64 `json:"limit_customer_tags"` // 允许兑换的成员 + ScorePrice int `json:"score_price"` + MarketPrice float64 `json:"market_price"` + CostPrice int `json:"cost_price"` + Stock int `json:"stock"` + Props []Prop `json:"props"` + Skus []Sku `json:"skus"` + ExpressType []string `json:"express_type"` + Comments []GiftComment `json:"comments"` + Content string `json:"content"` + AtLeastRechargeCount int `json:"at_least_recharge_count"` + Status int `json:"status"` + } + + type User struct { + Id int + Passport string + } + + table := "jfy_gift" + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1380.sql`), ";") + for _, v := range array { + if _, err := db.Exec(ctx, v); err != nil { + gtest.Error(err) + } + } + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var ( + entity = new(GiftEntity) + err = db.Model(table).Where("id", 17).Scan(entity) + ) + t.AssertNil(err) + t.Assert(len(entity.Skus), 2) + + t.Assert(entity.Skus[0].Name, "red") + t.Assert(entity.Skus[0].Stock, 10) + t.Assert(entity.Skus[0].GiftId, 1) + t.Assert(entity.Skus[0].CostPrice, 80) + t.Assert(entity.Skus[0].ScorePrice, 188) + t.Assert(entity.Skus[0].MarketPrice, 388) + + t.Assert(entity.Skus[1].Name, "blue") + t.Assert(entity.Skus[1].Stock, 100) + t.Assert(entity.Skus[1].GiftId, 2) + t.Assert(entity.Skus[1].CostPrice, 81) + t.Assert(entity.Skus[1].ScorePrice, 200) + t.Assert(entity.Skus[1].MarketPrice, 288) + + t.Assert(entity.Id, 17) + t.Assert(entity.StoreId, 100004) + t.Assert(entity.GiftType, 1) + t.Assert(entity.GiftName, "GIFT") + t.Assert(entity.Description, "支持个性定制的父亲节老师长辈的专属礼物") + t.Assert(len(entity.Covers.List), 3) + t.Assert(entity.OutSn, "259402") + t.Assert(entity.LimitCustomerTags, "[]") + t.Assert(entity.ScorePrice, 10) + t.Assert(len(entity.Props), 1) + t.Assert(len(entity.Comments), 2) + t.Assert(entity.Status, 99) + t.Assert(entity.Content, `

礼品详情

`) + }) +} + // https://github.com/gogf/gf/issues/1934 func Test_Issue1934(t *testing.T) { table := createInitTable() @@ -170,7 +286,7 @@ func Test_Issue1401(t *testing.T) { table1 = "parcels" table2 = "parcel_items" ) - array := gstr.SplitAndTrim(gtest.DataContent(`issue1401.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1401.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -212,7 +328,7 @@ func Test_Issue1412(t *testing.T) { table1 = "parcels" table2 = "items" ) - array := gstr.SplitAndTrim(gtest.DataContent(`issue1412.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `1412.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -474,7 +590,7 @@ func Test_Issue2012(t *testing.T) { // https://github.com/gogf/gf/issues/2105 func Test_Issue2105(t *testing.T) { table := "issue2105" - array := gstr.SplitAndTrim(gtest.DataContent(`issue2105.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `2105.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -772,7 +888,7 @@ func Test_Issue2561(t *testing.T) { // https://github.com/gogf/gf/issues/2439 func Test_Issue2439(t *testing.T) { gtest.C(t, func(t *gtest.T) { - array := gstr.SplitAndTrim(gtest.DataContent(`issue2439.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `2439.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -868,7 +984,7 @@ func Test_Issue2907(t *testing.T) { // https://github.com/gogf/gf/issues/3086 func Test_Issue3086(t *testing.T) { table := "issue3086_user" - array := gstr.SplitAndTrim(gtest.DataContent(`issue3086.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `3086.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -1013,7 +1129,7 @@ func Test_Issue3204(t *testing.T) { // https://github.com/gogf/gf/issues/3218 func Test_Issue3218(t *testing.T) { table := "issue3218_sys_config" - array := gstr.SplitAndTrim(gtest.DataContent(`issue3218.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `3218.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -1136,7 +1252,7 @@ func Test_Issue2552_ClearTableFields(t *testing.T) { // https://github.com/gogf/gf/issues/2643 func Test_Issue2643(t *testing.T) { table := "issue2643" - array := gstr.SplitAndTrim(gtest.DataContent(`issue2643.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `2643.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -1221,7 +1337,7 @@ func Test_Issue3649(t *testing.T) { // https://github.com/gogf/gf/issues/3754 func Test_Issue3754(t *testing.T) { table := "issue3754" - array := gstr.SplitAndTrim(gtest.DataContent(`issue3754.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `3754.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -1283,7 +1399,7 @@ func Test_Issue3754(t *testing.T) { // https://github.com/gogf/gf/issues/3626 func Test_Issue3626(t *testing.T) { table := "issue3626" - array := gstr.SplitAndTrim(gtest.DataContent(`issue3626.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `3626.sql`), ";") defer dropTable(table) for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { @@ -1413,7 +1529,7 @@ func Test_Issue3968(t *testing.T) { // https://github.com/gogf/gf/issues/3915 func Test_Issue3915(t *testing.T) { table := "issue3915" - array := gstr.SplitAndTrim(gtest.DataContent(`issue3915.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `3915.sql`), ";") for _, v := range array { if _, err := db.Exec(ctx, v); err != nil { gtest.Error(err) @@ -1500,7 +1616,7 @@ func Test_Issue2119(t *testing.T) { defer dropTable(tables[0]) defer dropTable(tables[1]) _ = tables - array := gstr.SplitAndTrim(gtest.DataContent(`issue2119.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `2119.sql`), ";") for _, v := range array { _, err := db.Exec(ctx, v) t.AssertNil(err) @@ -1561,7 +1677,7 @@ func Test_Issue2119(t *testing.T) { func Test_Issue4034(t *testing.T) { gtest.C(t, func(t *gtest.T) { table := "issue4034" - array := gstr.SplitAndTrim(gtest.DataContent(`issue4034.sql`), ";") + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `4034.sql`), ";") for _, v := range array { _, err := db.Exec(ctx, v) t.AssertNil(err) @@ -1590,3 +1706,162 @@ func issue4034SaveAppDevice(ctx context.Context, table string, tx gdb.TX) error }).Save() return err } + +// https://github.com/gogf/gf/issues/4086 +func Test_Issue4086(t *testing.T) { + table := "issue4086" + defer dropTable(table) + array := gstr.SplitAndTrim(gtest.DataContent(`issues`, `4086.sql`), ";") + for _, v := range array { + _, err := db.Exec(ctx, v) + gtest.AssertNil(err) + } + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []int64 `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []float32 `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []string `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos []any `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: nil, + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: nil, + }, + }) + }) + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds []int64 `json:"recommendIds" orm:"recommend_ids"` + Photos string `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: []int64{584, 585}, + Photos: "null", + }, + { + ProxyId: 2, + RecommendIds: []int64{}, + Photos: "", + }, + }) + }) + gtest.C(t, func(t *gtest.T) { + type ProxyParam struct { + ProxyId int64 `json:"proxyId" orm:"proxy_id"` + RecommendIds string `json:"recommendIds" orm:"recommend_ids"` + Photos json.RawMessage `json:"photos" orm:"photos"` + } + + var proxyParamList []*ProxyParam + err := db.Model(table).Ctx(ctx).Scan(&proxyParamList) + t.AssertNil(err) + t.Assert(len(proxyParamList), 2) + t.Assert(proxyParamList, []*ProxyParam{ + { + ProxyId: 1, + RecommendIds: "[584, 585]", + Photos: json.RawMessage("null"), + }, + { + ProxyId: 2, + RecommendIds: "[]", + Photos: json.RawMessage("null"), + }, + }) + }) +} diff --git a/contrib/drivers/mysql/mysql_z_unit_model_test.go b/contrib/drivers/mysql/mysql_z_unit_model_test.go index d28f461c241..e4190e66312 100644 --- a/contrib/drivers/mysql/mysql_z_unit_model_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_model_test.go @@ -682,6 +682,7 @@ func Test_Model_Array(t *testing.T) { t.Assert(all.Array("id"), g.Slice{1, 2, 3}) t.Assert(all.Array("nickname"), g.Slice{"name_1", "name_2", "name_3"}) }) + gtest.C(t, func(t *gtest.T) { array, err := db.Model(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array() t.AssertNil(err) @@ -757,6 +758,7 @@ func Test_Model_Value_WithCache(t *testing.T) { t.AssertNil(err) t.Assert(value.Int(), 0) }) + gtest.C(t, func(t *gtest.T) { result, err := db.Model(table).Data(g.MapStrAny{ "id": 1, @@ -1437,6 +1439,7 @@ func Test_Model_Option_Map(t *testing.T) { t.AssertNE(one["nickname"].String(), "1") t.Assert(one["passport"].String(), "1") }) + gtest.C(t, func(t *gtest.T) { table := createTable() defer dropTable(table) diff --git a/contrib/drivers/mysql/testdata/issue1380.sql b/contrib/drivers/mysql/testdata/issues/1380.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue1380.sql rename to contrib/drivers/mysql/testdata/issues/1380.sql diff --git a/contrib/drivers/mysql/testdata/issue1401.sql b/contrib/drivers/mysql/testdata/issues/1401.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue1401.sql rename to contrib/drivers/mysql/testdata/issues/1401.sql diff --git a/contrib/drivers/mysql/testdata/issue1412.sql b/contrib/drivers/mysql/testdata/issues/1412.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue1412.sql rename to contrib/drivers/mysql/testdata/issues/1412.sql diff --git a/contrib/drivers/mysql/testdata/issue2105.sql b/contrib/drivers/mysql/testdata/issues/2105.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue2105.sql rename to contrib/drivers/mysql/testdata/issues/2105.sql diff --git a/contrib/drivers/mysql/testdata/issue2119.sql b/contrib/drivers/mysql/testdata/issues/2119.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue2119.sql rename to contrib/drivers/mysql/testdata/issues/2119.sql diff --git a/contrib/drivers/mysql/testdata/issue2439.sql b/contrib/drivers/mysql/testdata/issues/2439.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue2439.sql rename to contrib/drivers/mysql/testdata/issues/2439.sql diff --git a/contrib/drivers/mysql/testdata/issue2643.sql b/contrib/drivers/mysql/testdata/issues/2643.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue2643.sql rename to contrib/drivers/mysql/testdata/issues/2643.sql diff --git a/contrib/drivers/mysql/testdata/issue3086.sql b/contrib/drivers/mysql/testdata/issues/3086.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue3086.sql rename to contrib/drivers/mysql/testdata/issues/3086.sql diff --git a/contrib/drivers/mysql/testdata/issue3218.sql b/contrib/drivers/mysql/testdata/issues/3218.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue3218.sql rename to contrib/drivers/mysql/testdata/issues/3218.sql diff --git a/contrib/drivers/mysql/testdata/issue3626.sql b/contrib/drivers/mysql/testdata/issues/3626.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue3626.sql rename to contrib/drivers/mysql/testdata/issues/3626.sql diff --git a/contrib/drivers/mysql/testdata/issue3754.sql b/contrib/drivers/mysql/testdata/issues/3754.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue3754.sql rename to contrib/drivers/mysql/testdata/issues/3754.sql diff --git a/contrib/drivers/mysql/testdata/issue3915.sql b/contrib/drivers/mysql/testdata/issues/3915.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue3915.sql rename to contrib/drivers/mysql/testdata/issues/3915.sql diff --git a/contrib/drivers/mysql/testdata/issue4034.sql b/contrib/drivers/mysql/testdata/issues/4034.sql similarity index 100% rename from contrib/drivers/mysql/testdata/issue4034.sql rename to contrib/drivers/mysql/testdata/issues/4034.sql diff --git a/contrib/drivers/mysql/testdata/issues/4086.sql b/contrib/drivers/mysql/testdata/issues/4086.sql new file mode 100644 index 00000000000..5e7ba66e1c5 --- /dev/null +++ b/contrib/drivers/mysql/testdata/issues/4086.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `issue4086`; +CREATE TABLE `issue4086` ( + `proxy_id` bigint NOT NULL, + `recommend_ids` json DEFAULT NULL, + `photos` json DEFAULT NULL, + PRIMARY KEY (`proxy_id`) +) ENGINE=InnoDB; + +INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (1, '[584, 585]', 'null'); +INSERT INTO `issue4086` (`proxy_id`, `recommend_ids`, `photos`) VALUES (2, '[]', NULL); \ No newline at end of file diff --git a/contrib/drivers/pgsql/pgsql_convert.go b/contrib/drivers/pgsql/pgsql_convert.go index d2652d307c8..c83a1e02918 100644 --- a/contrib/drivers/pgsql/pgsql_convert.go +++ b/contrib/drivers/pgsql/pgsql_convert.go @@ -124,7 +124,7 @@ func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fie // String slice. case "_varchar", "_text": - var result pq.StringArray + var result = make(pq.StringArray, 0) if err := result.Scan(fieldValue); err != nil { return nil, err } diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go index 558ca92c957..a1ea177dd24 100644 --- a/database/gdb/gdb.go +++ b/database/gdb/gdb.go @@ -516,8 +516,9 @@ type Core struct { links *gmap.Map // links caches all created links by node. logger glog.ILogger // Logger for logging functionality. config *ConfigNode // Current config node. + localTypeMap *gmap.StrAnyMap // Local type map for database field type conversion. dynamicConfig dynamicConfig // Dynamic configurations, which can be changed in runtime. - innerMemCache *gcache.Cache + innerMemCache *gcache.Cache // Internal memory cache for storing temporary data. } type dynamicConfig struct { @@ -768,6 +769,8 @@ const ( SqlTypeStmtQueryRowContext SqlType = "DB.Statement.QueryRowContext" ) +// LocalType is a type that defines the local storage type of a field value. +// It is used to specify how the field value should be processed locally. type LocalType string const ( @@ -925,6 +928,7 @@ func newDBByConfigNode(node *ConfigNode, group string) (db DB, err error) { links: gmap.New(true), logger: glog.New(), config: node, + localTypeMap: gmap.NewStrAnyMap(true), innerMemCache: gcache.New(), dynamicConfig: dynamicConfig{ MaxIdleConnCount: node.MaxIdleConnCount, diff --git a/database/gdb/gdb_converter.go b/database/gdb/gdb_converter.go new file mode 100644 index 00000000000..486ca61d5de --- /dev/null +++ b/database/gdb/gdb_converter.go @@ -0,0 +1,82 @@ +// 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 gdb + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/util/gconv" +) + +// iVal is used for type assert api for Val(). +type iVal interface { + Val() any +} + +var ( + // converter is the internal type converter for gdb. + converter = gconv.NewConverter() +) + +func init() { + converter.RegisterAnyConverterFunc( + sliceTypeConverterFunc, + reflect.TypeOf([]string{}), + reflect.TypeOf([]float32{}), + reflect.TypeOf([]float64{}), + reflect.TypeOf([]int{}), + reflect.TypeOf([]int32{}), + reflect.TypeOf([]int64{}), + reflect.TypeOf([]uint{}), + reflect.TypeOf([]uint32{}), + reflect.TypeOf([]uint64{}), + ) +} + +// GetConverter returns the internal type converter for gdb. +func GetConverter() gconv.Converter { + return converter +} + +func sliceTypeConverterFunc(from any, to reflect.Value) (err error) { + v, ok := from.(iVal) + if !ok { + return nil + } + fromVal := v.Val() + switch x := fromVal.(type) { + case []byte: + dst := to.Addr().Interface() + err = json.Unmarshal(x, dst) + case string: + dst := to.Addr().Interface() + err = json.Unmarshal([]byte(x), dst) + default: + fromType := reflect.TypeOf(fromVal) + switch fromType.Kind() { + case reflect.Slice: + convertOption := gconv.ConvertOption{ + SliceOption: gconv.SliceOption{ContinueOnError: true}, + MapOption: gconv.MapOption{ContinueOnError: true}, + StructOption: gconv.StructOption{ContinueOnError: true}, + } + dv, err := converter.ConvertWithTypeName(fromVal, to.Type().String(), convertOption) + if err != nil { + return err + } + to.Set(reflect.ValueOf(dv)) + default: + err = gerror.Newf( + `unsupported type converting from type "%T" to type "%T"`, + fromVal, to, + ) + } + } + return err +} diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index f4faa0bf4f9..3665841a364 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -223,6 +223,8 @@ func (c *Core) GetScan(ctx context.Context, pointer interface{}, sql string, arg case reflect.Struct: return c.db.GetCore().doGetStruct(ctx, pointer, sql, args...) + + default: } return gerror.NewCodef( gcode.CodeInvalidParameter, @@ -735,6 +737,7 @@ func (c *Core) HasTable(name string) (bool, error) { return false, nil } +// GetInnerMemCache retrieves and returns the inner memory cache object. func (c *Core) GetInnerMemCache() *gcache.Cache { return c.innerMemCache } diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go index bbb9df7574c..a954e03f97e 100644 --- a/database/gdb/gdb_core_structure.go +++ b/database/gdb/gdb_core_structure.go @@ -223,7 +223,9 @@ Default: } // CheckLocalTypeForField checks and returns corresponding type for given db type. -func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) { +// The `fieldType` is retrieved from ColumnTypes of db driver, example: +// UNSIGNED INT +func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, _ interface{}) (LocalType, error) { var ( typeName string typePattern string @@ -233,7 +235,12 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie typeName = gstr.Trim(match[1]) typePattern = gstr.Trim(match[2]) } else { - typeName = gstr.Split(fieldType, " ")[0] + var array = gstr.SplitAndTrim(fieldType, " ") + if len(array) > 1 && gstr.Equal(array[0], "unsigned") { + typeName = array[1] + } else if len(array) > 0 { + typeName = array[0] + } } typeName = strings.ToLower(typeName) @@ -291,11 +298,6 @@ func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fie if typePattern == "1" { return LocalTypeBool, nil } - s := gconv.String(fieldValue) - // mssql is true|false string. - if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") { - return LocalTypeBool, nil - } if gstr.ContainsI(fieldType, "unsigned") { return LocalTypeUint64Bytes, nil } diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go index 6e06ff0a50f..d7d8d1c510c 100644 --- a/database/gdb/gdb_core_underlying.go +++ b/database/gdb/gdb_core_underlying.go @@ -478,8 +478,11 @@ func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error) // which will cause struct converting issue. record[columnTypes[i].Name()] = nil } else { - var convertedValue interface{} - if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnTypes[i]); err != nil { + var ( + convertedValue interface{} + columnType = columnTypes[i] + ) + if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnType); err != nil { return nil, err } record[columnTypes[i].Name()] = gvar.New(convertedValue) @@ -498,7 +501,9 @@ func (c *Core) OrderRandomFunction() string { return "RAND()" } -func (c *Core) columnValueToLocalValue(ctx context.Context, value interface{}, columnType *sql.ColumnType) (interface{}, error) { +func (c *Core) columnValueToLocalValue( + ctx context.Context, value interface{}, columnType *sql.ColumnType, +) (interface{}, error) { var scanType = columnType.ScanType() if scanType != nil { // Common basic builtin types. diff --git a/database/gdb/gdb_type_record.go b/database/gdb/gdb_type_record.go index 2b14c537573..93470551026 100644 --- a/database/gdb/gdb_type_record.go +++ b/database/gdb/gdb_type_record.go @@ -53,7 +53,10 @@ func (r Record) Struct(pointer interface{}) error { } return nil } - return gconv.StructTag(r, pointer, OrmTagForStruct) + return converter.Struct(r, pointer, gconv.StructOption{ + PriorityTag: OrmTagForStruct, + ContinueOnError: true, + }) } // IsEmpty checks and returns whether `r` is empty. diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index 03e7051d3a9..8c02d828c8c 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -200,5 +200,12 @@ func (r Result) Structs(pointer interface{}) (err error) { } return nil } - return gconv.StructsTag(r, pointer, OrmTagForStruct) + var ( + sliceOption = gconv.SliceOption{ContinueOnError: true} + mapOption = gconv.StructOption{ + PriorityTag: OrmTagForStruct, + ContinueOnError: true, + } + ) + return converter.Structs(r, pointer, sliceOption, mapOption) } diff --git a/database/gdb/gdb_z_mysql_internal_test.go b/database/gdb/gdb_z_mysql_internal_test.go index 8539c2c9481..5b5872eb84b 100644 --- a/database/gdb/gdb_z_mysql_internal_test.go +++ b/database/gdb/gdb_z_mysql_internal_test.go @@ -14,6 +14,15 @@ import ( "github.com/gogf/gf/v2/text/gregex" ) +func Test_GetConverter(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + c := GetConverter() + s, err := c.String(1) + t.AssertNil(err) + t.AssertEQ(s, "1") + }) +} + func Test_HookSelect_Regex(t *testing.T) { gtest.C(t, func(t *gtest.T) { var ( diff --git a/examples b/examples index 2544fee34dd..c454db2549a 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 2544fee34dd10e6914687f7335bd9e772215ce07 +Subproject commit c454db2549ac7ecce844b687c1e610461818830d diff --git a/net/ghttp/ghttp_z_unit_feature_config_test.go b/net/ghttp/ghttp_z_unit_feature_config_test.go index c84cf24255e..306d61e97e4 100644 --- a/net/ghttp/ghttp_z_unit_feature_config_test.go +++ b/net/ghttp/ghttp_z_unit_feature_config_test.go @@ -30,15 +30,15 @@ func Test_ConfigFromMap(t *testing.T) { "readTimeout": "60s", "indexFiles": g.Slice{"index.php", "main.php"}, "errorLogEnabled": true, - "cookieMaxAge": "1y", + "cookieMaxAge": "1d", "cookieSameSite": "lax", "cookieSecure": true, "cookieHttpOnly": true, } config, err := ghttp.ConfigFromMap(m) t.AssertNil(err) - d1, _ := time.ParseDuration(gconv.String(m["readTimeout"])) - d2, _ := time.ParseDuration(gconv.String(m["cookieMaxAge"])) + d1, _ := gtime.ParseDuration(gconv.String(m["readTimeout"])) + d2, _ := gtime.ParseDuration(gconv.String(m["cookieMaxAge"])) t.Assert(config.Address, m["address"]) t.Assert(config.ReadTimeout, d1) t.Assert(config.CookieMaxAge, d2) diff --git a/net/ghttp/ghttp_z_unit_feature_router_strict_test.go b/net/ghttp/ghttp_z_unit_feature_router_strict_test.go index ef4fa12112b..c193c35a4b8 100644 --- a/net/ghttp/ghttp_z_unit_feature_router_strict_test.go +++ b/net/ghttp/ghttp_z_unit_feature_router_strict_test.go @@ -505,7 +505,6 @@ func (t *testNullStringIssue3465) Test(ctx context.Context, req *testNullStringI // https://github.com/gogf/gf/issues/3465 func Test_NullString_Issue3465(t *testing.T) { - s := g.Server(guid.S()) s.Use(ghttp.MiddlewareHandlerResponse) s.Group("/", func(group *ghttp.RouterGroup) { diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index d3bc027ef05..38302b1e3cf 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -10,36 +10,155 @@ package gconv import ( + "reflect" + "time" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/converter" "github.com/gogf/gf/v2/util/gconv/internal/localinterface" "github.com/gogf/gf/v2/util/gconv/internal/structcache" ) -var ( - // Empty strings. - emptyStringMap = map[string]struct{}{ - "": {}, - "0": {}, - "no": {}, - "off": {}, - "false": {}, - } +// Converter is the manager for type converting. +type Converter interface { + ConverterForConvert + ConverterForRegister + ConverterForInt + ConverterForUint + ConverterForTime + ConverterForFloat + ConverterForMap + ConverterForSlice + ConverterForStruct + ConverterForBasic +} + +// ConverterForBasic is the basic converting interface. +type ConverterForBasic interface { + Scan(srcValue, dstPointer any, option ScanOption) (err error) + String(any any) (string, error) + Bool(any any) (bool, error) + Rune(any any) (rune, error) +} + +// ConverterForTime is the converting interface for time. +type ConverterForTime interface { + Time(v any, format ...string) (time.Time, error) + Duration(v any) (time.Duration, error) + GTime(v any, format ...string) (*gtime.Time, error) +} + +// ConverterForInt is the converting interface for integer. +type ConverterForInt interface { + Int(v any) (int, error) + Int8(v any) (int8, error) + Int16(v any) (int16, error) + Int32(v any) (int32, error) + Int64(v any) (int64, error) +} + +// ConverterForUint is the converting interface for unsigned integer. +type ConverterForUint interface { + Uint(v any) (uint, error) + Uint8(v any) (uint8, error) + Uint16(v any) (uint16, error) + Uint32(v any) (uint32, error) + Uint64(v any) (uint64, error) +} + +// ConverterForFloat is the converting interface for float. +type ConverterForFloat interface { + Float32(v any) (float32, error) + Float64(v any) (float64, error) +} + +// ConverterForMap is the converting interface for map. +type ConverterForMap interface { + Map(v any, option MapOption) (map[string]any, error) + MapStrStr(v any, option MapOption) (map[string]string, error) +} + +// ConverterForSlice is the converting interface for slice. +type ConverterForSlice interface { + Bytes(v any) ([]byte, error) + Runes(v any) ([]rune, error) + SliceAny(v any, option SliceOption) ([]any, error) + SliceFloat32(v any, option SliceOption) ([]float32, error) + SliceFloat64(v any, option SliceOption) ([]float64, error) + SliceInt(v any, option SliceOption) ([]int, error) + SliceInt32(v any, option SliceOption) ([]int32, error) + SliceInt64(v any, option SliceOption) ([]int64, error) + SliceUint(v any, option SliceOption) ([]uint, error) + SliceUint32(v any, option SliceOption) ([]uint32, error) + SliceUint64(v any, option SliceOption) ([]uint64, error) + SliceStr(v any, option SliceOption) ([]string, error) + SliceMap(v any, sliceOption SliceOption, mapOption MapOption) ([]map[string]any, error) +} + +// ConverterForStruct is the converting interface for struct. +type ConverterForStruct interface { + Struct(params, pointer any, option StructOption) (err error) + Structs(params, pointer any, sliceOption SliceOption, structOption StructOption) (err error) +} + +// ConverterForConvert is the converting interface for custom converting. +type ConverterForConvert interface { + ConvertWithRefer(fromValue, referValue any, option ConvertOption) (any, error) + ConvertWithTypeName(fromValue any, toTypeName string, option ConvertOption) (any, error) +} + +// ConverterForRegister is the converting interface for custom converter registration. +type ConverterForRegister interface { + RegisterTypeConverterFunc(f any) error + RegisterAnyConverterFunc(f AnyConvertFunc, types ...reflect.Type) +} + +type ( + // AnyConvertFunc is the function type for converting any to specified type. + AnyConvertFunc = structcache.AnyConvertFunc + + // MapOption specifies the option for map converting. + MapOption = converter.MapOption + + // SliceOption is the option for Slice type converting. + SliceOption = converter.SliceOption + + // ScanOption is the option for the Scan function. + ScanOption = converter.ScanOption + + // StructOption is the option for Struct converting. + StructOption = converter.StructOption + + // ConvertOption is the option for converting. + ConvertOption = converter.ConvertOption ) // IUnmarshalValue is the interface for custom defined types customizing value assignment. // Note that only pointer can implement interface IUnmarshalValue. type IUnmarshalValue = localinterface.IUnmarshalValue -func init() { - // register common converters for internal usage. - structcache.RegisterCommonConverter(structcache.CommonConverter{ - Int64: Int64, - Uint64: Uint64, - String: String, - Float32: Float32, - Float64: Float64, - Time: Time, - GTime: GTime, - Bytes: Bytes, - Bool: Bool, - }) +var ( + // defaultConverter is the default management object converting. + defaultConverter = converter.NewConverter() +) + +// RegisterAnyConverterFunc registers custom type converting function for specified type. +func RegisterAnyConverterFunc(f AnyConvertFunc, types ...reflect.Type) { + defaultConverter.RegisterAnyConverterFunc(f, types...) +} + +// NewConverter creates and returns management object for type converting. +func NewConverter() Converter { + return converter.NewConverter() +} + +// RegisterConverter registers custom converter. +// Deprecated: use RegisterTypeConverterFunc instead for clear +func RegisterConverter(fn any) (err error) { + return RegisterTypeConverterFunc(fn) +} + +// RegisterTypeConverterFunc registers custom converter. +func RegisterTypeConverterFunc(fn any) (err error) { + return defaultConverter.RegisterTypeConverterFunc(fn) } diff --git a/util/gconv/gconv_basic.go b/util/gconv/gconv_basic.go index 56a10e07577..d4ab35eaf2f 100644 --- a/util/gconv/gconv_basic.go +++ b/util/gconv/gconv_basic.go @@ -6,331 +6,40 @@ package gconv -import ( - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/os/gtime" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Byte converts `any` to byte. func Byte(any any) byte { - v, _ := doByte(any) + v, _ := defaultConverter.Uint8(any) return v } -func doByte(any any) (byte, error) { - if v, ok := any.(byte); ok { - return v, nil - } - return doUint8(any) -} - // Bytes converts `any` to []byte. func Bytes(any any) []byte { - v, _ := doBytes(any) + v, _ := defaultConverter.Bytes(any) return v } -func doBytes(any any) ([]byte, error) { - if any == nil { - return nil, nil - } - switch value := any.(type) { - case string: - return []byte(value), nil - - case []byte: - return value, nil - - default: - if f, ok := value.(localinterface.IBytes); ok { - return f.Bytes(), nil - } - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Map: - bytes, err := json.Marshal(any) - if err != nil { - return nil, err - } - return bytes, nil - - case reflect.Array, reflect.Slice: - var ( - ok = true - bytes = make([]byte, originValueAndKind.OriginValue.Len()) - ) - for i := range bytes { - int32Value, err := doInt32(originValueAndKind.OriginValue.Index(i).Interface()) - if err != nil { - return nil, err - } - if int32Value < 0 || int32Value > math.MaxUint8 { - ok = false - break - } - bytes[i] = byte(int32Value) - } - if ok { - return bytes, nil - } - default: - } - return gbinary.Encode(any), nil - } -} - // Rune converts `any` to rune. func Rune(any any) rune { - v, _ := doRune(any) + v, _ := defaultConverter.Rune(any) return v } -func doRune(any any) (rune, error) { - if v, ok := any.(rune); ok { - return v, nil - } - v, err := doInt32(any) - if err != nil { - return 0, err - } - return v, nil -} - // Runes converts `any` to []rune. func Runes(any any) []rune { - v, _ := doRunes(any) + v, _ := defaultConverter.Runes(any) return v } -func doRunes(any any) ([]rune, error) { - if v, ok := any.([]rune); ok { - return v, nil - } - s, err := doString(any) - if err != nil { - return nil, err - } - return []rune(s), nil -} - // String converts `any` to string. // It's most commonly used converting function. func String(any any) string { - v, _ := doString(any) + v, _ := defaultConverter.String(any) return v } -func doString(any any) (string, error) { - if any == nil { - return "", nil - } - switch value := any.(type) { - case int: - return strconv.Itoa(value), nil - case int8: - return strconv.Itoa(int(value)), nil - case int16: - return strconv.Itoa(int(value)), nil - case int32: - return strconv.Itoa(int(value)), nil - case int64: - return strconv.FormatInt(value, 10), nil - case uint: - return strconv.FormatUint(uint64(value), 10), nil - case uint8: - return strconv.FormatUint(uint64(value), 10), nil - case uint16: - return strconv.FormatUint(uint64(value), 10), nil - case uint32: - return strconv.FormatUint(uint64(value), 10), nil - case uint64: - return strconv.FormatUint(value, 10), nil - case float32: - return strconv.FormatFloat(float64(value), 'f', -1, 32), nil - case float64: - return strconv.FormatFloat(value, 'f', -1, 64), nil - case bool: - return strconv.FormatBool(value), nil - case string: - return value, nil - case []byte: - return string(value), nil - case complex64, complex128: - return fmt.Sprintf("%v", value), nil - case time.Time: - if value.IsZero() { - return "", nil - } - return value.String(), nil - case *time.Time: - if value == nil { - return "", nil - } - return value.String(), nil - case gtime.Time: - if value.IsZero() { - return "", nil - } - return value.String(), nil - case *gtime.Time: - if value == nil { - return "", nil - } - return value.String(), nil - default: - if f, ok := value.(localinterface.IString); ok { - // If the variable implements the String() interface, - // then use that interface to perform the conversion - return f.String(), nil - } - if f, ok := value.(localinterface.IError); ok { - // If the variable implements the Error() interface, - // then use that interface to perform the conversion - return f.Error(), nil - } - // Reflect checks. - var ( - rv = reflect.ValueOf(value) - kind = rv.Kind() - ) - switch kind { - case - reflect.Chan, - reflect.Map, - reflect.Slice, - reflect.Func, - reflect.Interface, - reflect.UnsafePointer: - if rv.IsNil() { - return "", nil - } - case reflect.String: - return rv.String(), nil - case reflect.Ptr: - if rv.IsNil() { - return "", nil - } - return doString(rv.Elem().Interface()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(rv.Int(), 10), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(rv.Uint(), 10), nil - case reflect.Uintptr: - return strconv.FormatUint(rv.Uint(), 10), nil - case reflect.Float32, reflect.Float64: - return strconv.FormatFloat(rv.Float(), 'f', -1, 64), nil - case reflect.Bool: - return strconv.FormatBool(rv.Bool()), nil - default: - } - // Finally, we use json.Marshal to convert. - jsonContent, err := json.Marshal(value) - if err != nil { - return fmt.Sprint(value), gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "error marshaling value to JSON for: %v", value, - ) - } - return string(jsonContent), nil - } -} - // Bool converts `any` to bool. // It returns false if `any` is: false, "", 0, "false", "off", "no", empty slice/map. func Bool(any any) bool { - v, _ := doBool(any) + v, _ := defaultConverter.Bool(any) return v } - -func doBool(any any) (bool, error) { - if any == nil { - return false, nil - } - switch value := any.(type) { - case bool: - return value, nil - case []byte: - if _, ok := emptyStringMap[strings.ToLower(string(value))]; ok { - return false, nil - } - return true, nil - case string: - if _, ok := emptyStringMap[strings.ToLower(value)]; ok { - return false, nil - } - return true, nil - default: - if f, ok := value.(localinterface.IBool); ok { - return f.Bool(), nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Ptr: - if rv.IsNil() { - return false, nil - } - if rv.Type().Elem().Kind() == reflect.Bool { - return rv.Elem().Bool(), nil - } - return doBool(rv.Elem().Interface()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int() != 0, nil - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rv.Uint() != 0, nil - case reflect.Float32, reflect.Float64: - return rv.Float() != 0, nil - case reflect.Bool: - return rv.Bool(), nil - // TODO:(Map,Array,Slice,Struct) It might panic here for these types. - case reflect.Map, reflect.Array: - fallthrough - case reflect.Slice: - return rv.Len() != 0, nil - case reflect.Struct: - return true, nil - default: - s, err := doString(any) - if err != nil { - return false, err - } - if _, ok := emptyStringMap[strings.ToLower(s)]; ok { - return false, nil - } - return true, nil - } - } -} - -// checkJsonAndUnmarshalUseNumber checks if given `any` is JSON formatted string value and does converting using `json.UnmarshalUseNumber`. -func checkJsonAndUnmarshalUseNumber(any any, target any) bool { - switch r := any.(type) { - case []byte: - if json.Valid(r) { - if err := json.UnmarshalUseNumber(r, &target); err != nil { - return false - } - return true - } - - case string: - anyAsBytes := []byte(r) - if json.Valid(anyAsBytes) { - if err := json.UnmarshalUseNumber(anyAsBytes, &target); err != nil { - return false - } - return true - } - } - return false -} diff --git a/util/gconv/gconv_convert.go b/util/gconv/gconv_convert.go index 49bb32b1fd3..eb47a6d4b93 100644 --- a/util/gconv/gconv_convert.go +++ b/util/gconv/gconv_convert.go @@ -6,353 +6,30 @@ package gconv -import ( - "context" - "reflect" - "time" - - "github.com/gogf/gf/v2/internal/intlog" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gtime" -) - // Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string. // // The optional parameter `extraParams` is used for additional necessary parameter for this conversion. // It supports common basic types conversion as its conversion based on type name string. -func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} { - return doConvert(doConvertInput{ - FromValue: fromValue, - ToTypeName: toTypeName, - ReferValue: nil, - Extra: extraParams, +func Convert(fromValue any, toTypeName string, extraParams ...any) any { + result, _ := defaultConverter.ConvertWithTypeName(fromValue, toTypeName, ConvertOption{ + ExtraParams: extraParams, + SliceOption: SliceOption{ContinueOnError: true}, + MapOption: MapOption{ContinueOnError: true}, + StructOption: StructOption{ContinueOnError: true}, }) + return result } // ConvertWithRefer converts the variable `fromValue` to the type referred by value `referValue`. // // The optional parameter `extraParams` is used for additional necessary parameter for this conversion. // It supports common basic types conversion as its conversion based on type name string. -func ConvertWithRefer(fromValue interface{}, referValue interface{}, extraParams ...interface{}) interface{} { - var referValueRf reflect.Value - if v, ok := referValue.(reflect.Value); ok { - referValueRf = v - } else { - referValueRf = reflect.ValueOf(referValue) - } - return doConvert(doConvertInput{ - FromValue: fromValue, - ToTypeName: referValueRf.Type().String(), - ReferValue: referValue, - Extra: extraParams, +func ConvertWithRefer(fromValue any, referValue any, extraParams ...any) any { + result, _ := defaultConverter.ConvertWithRefer(fromValue, referValue, ConvertOption{ + ExtraParams: extraParams, + SliceOption: SliceOption{ContinueOnError: true}, + MapOption: MapOption{ContinueOnError: true}, + StructOption: StructOption{ContinueOnError: true}, }) -} - -type doConvertInput struct { - FromValue interface{} // Value that is converted from. - ToTypeName string // Target value type name in string. - ReferValue interface{} // Referred value, a value in type `ToTypeName`. Note that its type might be reflect.Value. - Extra []interface{} // Extra values for implementing the converting. - // Marks that the value is already converted and set to `ReferValue`. Caller can ignore the returned result. - // It is an attribute for internal usage purpose. - alreadySetToReferValue bool -} - -// doConvert does commonly use types converting. -func doConvert(in doConvertInput) (convertedValue interface{}) { - switch in.ToTypeName { - case "int": - return Int(in.FromValue) - case "*int": - if _, ok := in.FromValue.(*int); ok { - return in.FromValue - } - v := Int(in.FromValue) - return &v - - case "int8": - return Int8(in.FromValue) - case "*int8": - if _, ok := in.FromValue.(*int8); ok { - return in.FromValue - } - v := Int8(in.FromValue) - return &v - - case "int16": - return Int16(in.FromValue) - case "*int16": - if _, ok := in.FromValue.(*int16); ok { - return in.FromValue - } - v := Int16(in.FromValue) - return &v - - case "int32": - return Int32(in.FromValue) - case "*int32": - if _, ok := in.FromValue.(*int32); ok { - return in.FromValue - } - v := Int32(in.FromValue) - return &v - - case "int64": - return Int64(in.FromValue) - case "*int64": - if _, ok := in.FromValue.(*int64); ok { - return in.FromValue - } - v := Int64(in.FromValue) - return &v - - case "uint": - return Uint(in.FromValue) - case "*uint": - if _, ok := in.FromValue.(*uint); ok { - return in.FromValue - } - v := Uint(in.FromValue) - return &v - - case "uint8": - return Uint8(in.FromValue) - case "*uint8": - if _, ok := in.FromValue.(*uint8); ok { - return in.FromValue - } - v := Uint8(in.FromValue) - return &v - - case "uint16": - return Uint16(in.FromValue) - case "*uint16": - if _, ok := in.FromValue.(*uint16); ok { - return in.FromValue - } - v := Uint16(in.FromValue) - return &v - - case "uint32": - return Uint32(in.FromValue) - case "*uint32": - if _, ok := in.FromValue.(*uint32); ok { - return in.FromValue - } - v := Uint32(in.FromValue) - return &v - - case "uint64": - return Uint64(in.FromValue) - case "*uint64": - if _, ok := in.FromValue.(*uint64); ok { - return in.FromValue - } - v := Uint64(in.FromValue) - return &v - - case "float32": - return Float32(in.FromValue) - case "*float32": - if _, ok := in.FromValue.(*float32); ok { - return in.FromValue - } - v := Float32(in.FromValue) - return &v - - case "float64": - return Float64(in.FromValue) - case "*float64": - if _, ok := in.FromValue.(*float64); ok { - return in.FromValue - } - v := Float64(in.FromValue) - return &v - - case "bool": - return Bool(in.FromValue) - case "*bool": - if _, ok := in.FromValue.(*bool); ok { - return in.FromValue - } - v := Bool(in.FromValue) - return &v - - case "string": - return String(in.FromValue) - case "*string": - if _, ok := in.FromValue.(*string); ok { - return in.FromValue - } - v := String(in.FromValue) - return &v - - case "[]byte": - return Bytes(in.FromValue) - case "[]int": - return Ints(in.FromValue) - case "[]int32": - return Int32s(in.FromValue) - case "[]int64": - return Int64s(in.FromValue) - case "[]uint": - return Uints(in.FromValue) - case "[]uint8": - return Bytes(in.FromValue) - case "[]uint32": - return Uint32s(in.FromValue) - case "[]uint64": - return Uint64s(in.FromValue) - case "[]float32": - return Float32s(in.FromValue) - case "[]float64": - return Float64s(in.FromValue) - case "[]string": - return Strings(in.FromValue) - - case "Time", "time.Time": - if len(in.Extra) > 0 { - return Time(in.FromValue, String(in.Extra[0])) - } - return Time(in.FromValue) - case "*time.Time": - var v time.Time - if len(in.Extra) > 0 { - v = Time(in.FromValue, String(in.Extra[0])) - } else { - if _, ok := in.FromValue.(*time.Time); ok { - return in.FromValue - } - v = Time(in.FromValue) - } - return &v - - case "GTime", "gtime.Time": - if len(in.Extra) > 0 { - if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { - return *v - } else { - return *gtime.New() - } - } - if v := GTime(in.FromValue); v != nil { - return *v - } else { - return *gtime.New() - } - case "*gtime.Time": - if len(in.Extra) > 0 { - if v := GTime(in.FromValue, String(in.Extra[0])); v != nil { - return v - } else { - return gtime.New() - } - } - if v := GTime(in.FromValue); v != nil { - return v - } else { - return gtime.New() - } - - case "Duration", "time.Duration": - return Duration(in.FromValue) - case "*time.Duration": - if _, ok := in.FromValue.(*time.Duration); ok { - return in.FromValue - } - v := Duration(in.FromValue) - return &v - - case "map[string]string": - return MapStrStr(in.FromValue) - - case "map[string]interface {}": - return Map(in.FromValue) - - case "[]map[string]interface {}": - return Maps(in.FromValue) - - case "RawMessage", "json.RawMessage": - // issue 3449 - bytes, err := json.Marshal(in.FromValue) - if err != nil { - intlog.Errorf(context.TODO(), `%+v`, err) - } - return bytes - - default: - if in.ReferValue != nil { - var referReflectValue reflect.Value - if v, ok := in.ReferValue.(reflect.Value); ok { - referReflectValue = v - } else { - referReflectValue = reflect.ValueOf(in.ReferValue) - } - var fromReflectValue reflect.Value - if v, ok := in.FromValue.(reflect.Value); ok { - fromReflectValue = v - } else { - fromReflectValue = reflect.ValueOf(in.FromValue) - } - - // custom converter. - if dstReflectValue, ok, _ := callCustomConverterWithRefer(fromReflectValue, referReflectValue); ok { - return dstReflectValue.Interface() - } - - defer func() { - if recover() != nil { - in.alreadySetToReferValue = false - if err := bindVarToReflectValue(referReflectValue, in.FromValue, nil); err == nil { - in.alreadySetToReferValue = true - convertedValue = referReflectValue.Interface() - } - } - }() - switch referReflectValue.Kind() { - case reflect.Ptr: - // Type converting for custom type pointers. - // Eg: - // type PayMode int - // type Req struct{ - // Mode *PayMode - // } - // - // Struct(`{"Mode": 1000}`, &req) - originType := referReflectValue.Type().Elem() - switch originType.Kind() { - case reflect.Struct: - // Not support some kinds. - default: - in.ToTypeName = originType.Kind().String() - in.ReferValue = nil - refElementValue := reflect.ValueOf(doConvert(in)) - originTypeValue := reflect.New(refElementValue.Type()).Elem() - originTypeValue.Set(refElementValue) - in.alreadySetToReferValue = true - return originTypeValue.Addr().Convert(referReflectValue.Type()).Interface() - } - - case reflect.Map: - var targetValue = reflect.New(referReflectValue.Type()).Elem() - if err := doMapToMap(in.FromValue, targetValue); err == nil { - in.alreadySetToReferValue = true - } - return targetValue.Interface() - } - in.ToTypeName = referReflectValue.Kind().String() - in.ReferValue = nil - in.alreadySetToReferValue = true - convertedValue = reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface() - return convertedValue - } - return in.FromValue - } -} - -func doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput) { - convertedValue := doConvert(in) - if !in.alreadySetToReferValue { - reflectValue.Set(reflect.ValueOf(convertedValue)) - } + return result } diff --git a/util/gconv/gconv_converter.go b/util/gconv/gconv_converter.go deleted file mode 100644 index eb0e226d1c0..00000000000 --- a/util/gconv/gconv_converter.go +++ /dev/null @@ -1,187 +0,0 @@ -// 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 ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/util/gconv/internal/structcache" -) - -type ( - converterInType = reflect.Type - converterOutType = reflect.Type - converterFunc = reflect.Value -) - -// customConverters for internal converter storing. -var customConverters = make(map[converterInType]map[converterOutType]converterFunc) - -// RegisterConverter to register custom converter. -// It must be registered before you use this custom converting feature. -// It is suggested to do it in boot procedure of the process. -// -// Note: -// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`. -// It will convert type `T1` to type `T2`. -// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer. -func RegisterConverter(fn interface{}) (err error) { - var ( - fnReflectType = reflect.TypeOf(fn) - errType = reflect.TypeOf((*error)(nil)).Elem() - ) - if fnReflectType.Kind() != reflect.Func || - fnReflectType.NumIn() != 1 || fnReflectType.NumOut() != 2 || - !fnReflectType.Out(1).Implements(errType) { - err = gerror.NewCodef( - gcode.CodeInvalidParameter, - "parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, but defined as `%s`", - fnReflectType.String(), - ) - return - } - - // The Key and Value of the converter map should not be pointer. - var ( - inType = fnReflectType.In(0) - outType = fnReflectType.Out(0) - ) - if inType.Kind() == reflect.Pointer { - err = gerror.NewCodef( - gcode.CodeInvalidParameter, - "invalid converter function `%s`: invalid input parameter type `%s`, should not be type of pointer", - fnReflectType.String(), inType.String(), - ) - return - } - if outType.Kind() != reflect.Pointer { - err = gerror.NewCodef( - gcode.CodeInvalidParameter, - "invalid converter function `%s`: invalid output parameter type `%s` should be type of pointer", - fnReflectType.String(), outType.String(), - ) - return - } - - registeredOutTypeMap, ok := customConverters[inType] - if !ok { - registeredOutTypeMap = make(map[converterOutType]converterFunc) - customConverters[inType] = registeredOutTypeMap - } - if _, ok = registeredOutTypeMap[outType]; ok { - err = gerror.NewCodef( - gcode.CodeInvalidOperation, - "the converter parameter type `%s` to type `%s` has already been registered", - inType.String(), outType.String(), - ) - return - } - registeredOutTypeMap[outType] = reflect.ValueOf(fn) - structcache.RegisterCustomConvertType(outType) - return -} - -func getRegisteredConverterFuncAndSrcType( - srcReflectValue, dstReflectValueForRefer reflect.Value, -) (f converterFunc, srcType reflect.Type, ok bool) { - if len(customConverters) == 0 { - return reflect.Value{}, nil, false - } - srcType = srcReflectValue.Type() - for srcType.Kind() == reflect.Pointer { - srcType = srcType.Elem() - } - var registeredOutTypeMap map[converterOutType]converterFunc - // firstly, it searches the map by input parameter type. - registeredOutTypeMap, ok = customConverters[srcType] - if !ok { - return reflect.Value{}, nil, false - } - var dstType = dstReflectValueForRefer.Type() - if dstType.Kind() == reflect.Pointer { - // Might be **struct, which is support as designed. - if dstType.Elem().Kind() == reflect.Pointer { - dstType = dstType.Elem() - } - } else if dstReflectValueForRefer.IsValid() && dstReflectValueForRefer.CanAddr() { - dstType = dstReflectValueForRefer.Addr().Type() - } else { - dstType = reflect.PointerTo(dstType) - } - // secondly, it searches the input parameter type map - // and finds the result converter function by the output parameter type. - f, ok = registeredOutTypeMap[dstType] - if !ok { - return reflect.Value{}, nil, false - } - return -} - -func callCustomConverterWithRefer( - srcReflectValue, referReflectValue reflect.Value, -) (dstReflectValue reflect.Value, converted bool, err error) { - registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue) - if !ok { - return reflect.Value{}, false, nil - } - dstReflectValue = reflect.New(referReflectValue.Type()).Elem() - converted, err = doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) - return -} - -// callCustomConverter call the custom converter. It will try some possible type. -func callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) { - registeredConverterFunc, srcType, ok := getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue) - if !ok { - return false, nil - } - return doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) -} - -func doCallCustomConverter( - srcReflectValue reflect.Value, - dstReflectValue reflect.Value, - registeredConverterFunc converterFunc, - srcType reflect.Type, -) (converted bool, err error) { - // Converter function calling. - for srcReflectValue.Type() != srcType { - srcReflectValue = srcReflectValue.Elem() - } - result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue}) - if !result[1].IsNil() { - return false, result[1].Interface().(error) - } - // The `result[0]` is a pointer. - if result[0].IsNil() { - return false, nil - } - var resultValue = result[0] - for { - if resultValue.Type() == dstReflectValue.Type() && dstReflectValue.CanSet() { - dstReflectValue.Set(resultValue) - converted = true - } else if dstReflectValue.Kind() == reflect.Pointer { - if resultValue.Type() == dstReflectValue.Elem().Type() && dstReflectValue.Elem().CanSet() { - dstReflectValue.Elem().Set(resultValue) - converted = true - } - } - if converted { - break - } - if resultValue.Kind() == reflect.Pointer { - resultValue = resultValue.Elem() - } else { - break - } - } - - return converted, nil -} diff --git a/util/gconv/gconv_float.go b/util/gconv/gconv_float.go index 2f7204a5cf9..62d6d4861d5 100644 --- a/util/gconv/gconv_float.go +++ b/util/gconv/gconv_float.go @@ -6,143 +6,14 @@ package gconv -import ( - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Float32 converts `any` to float32. func Float32(any any) float32 { - v, _ := doFloat32(any) + v, _ := defaultConverter.Float32(any) return v } -func doFloat32(any any) (float32, error) { - if any == nil { - return 0, nil - } - switch value := any.(type) { - case float32: - return value, nil - case float64: - return float32(value), nil - case []byte: - // TODO: It might panic here for these types. - return gbinary.DecodeToFloat32(value), nil - default: - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float32(rv.Int()), nil - case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float32(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - return float32(rv.Float()), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.String: - f, err := strconv.ParseFloat(rv.String(), 32) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, - ) - } - return float32(f), nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := value.(localinterface.IFloat32); ok { - return f.Float32(), nil - } - return doFloat32(rv.Elem().Interface()) - default: - if f, ok := value.(localinterface.IFloat32); ok { - return f.Float32(), nil - } - v, err := strconv.ParseFloat(String(any), 32) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, - ) - } - return float32(v), nil - } - } -} - // Float64 converts `any` to float64. func Float64(any any) float64 { - v, _ := doFloat64(any) + v, _ := defaultConverter.Float64(any) return v } - -func doFloat64(any any) (float64, error) { - if any == nil { - return 0, nil - } - switch value := any.(type) { - case float32: - return float64(value), nil - case float64: - return value, nil - case []byte: - // TODO: It might panic here for these types. - return gbinary.DecodeToFloat64(value), nil - default: - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(rv.Int()), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float64(rv.Uint()), nil - case reflect.Uintptr: - return float64(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - // Please Note: - // When the type is float32 or a custom type defined based on float32, - // switching to float64 may result in a few extra decimal places. - return rv.Float(), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.String: - f, err := strconv.ParseFloat(rv.String(), 64) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, - ) - } - return f, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := value.(localinterface.IFloat64); ok { - return f.Float64(), nil - } - return doFloat64(rv.Elem().Interface()) - default: - if f, ok := value.(localinterface.IFloat64); ok { - return f.Float64(), nil - } - v, err := strconv.ParseFloat(String(any), 64) - if err != nil { - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, - ) - } - return v, nil - } - } -} diff --git a/util/gconv/gconv_int.go b/util/gconv/gconv_int.go index 7e1e6a129dd..78c534d6fa2 100644 --- a/util/gconv/gconv_int.go +++ b/util/gconv/gconv_int.go @@ -6,176 +6,32 @@ package gconv -import ( - "math" - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Int converts `any` to int. func Int(any any) int { - v, _ := doInt(any) + v, _ := defaultConverter.Int(any) return v } -func doInt(any any) (int, error) { - if v, ok := any.(int); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int(v), nil -} - // Int8 converts `any` to int8. func Int8(any any) int8 { - v, _ := doInt8(any) + v, _ := defaultConverter.Int8(any) return v } -func doInt8(any any) (int8, error) { - if v, ok := any.(int8); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int8(v), nil -} - // Int16 converts `any` to int16. func Int16(any any) int16 { - v, _ := doInt16(any) + v, _ := defaultConverter.Int16(any) return v } -func doInt16(any any) (int16, error) { - if v, ok := any.(int16); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int16(v), nil -} - // Int32 converts `any` to int32. func Int32(any any) int32 { - v, _ := doInt32(any) + v, _ := defaultConverter.Int32(any) return v } -func doInt32(any any) (int32, error) { - if v, ok := any.(int32); ok { - return v, nil - } - v, err := doInt64(any) - if err != nil { - return 0, err - } - return int32(v), nil -} - // Int64 converts `any` to int64. func Int64(any any) int64 { - v, _ := doInt64(any) + v, _ := defaultConverter.Int64(any) return v } - -func doInt64(any any) (int64, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(int64); ok { - return v, nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int(), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return int64(rv.Uint()), nil - case reflect.Uintptr: - return int64(rv.Uint()), nil - case reflect.Float32, reflect.Float64: - return int64(rv.Float()), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := any.(localinterface.IInt64); ok { - return f.Int64(), nil - } - return doInt64(rv.Elem().Interface()) - case reflect.Slice: - // TODO: It might panic here for these types. - if rv.Type().Elem().Kind() == reflect.Uint8 { - return gbinary.DecodeToInt64(rv.Bytes()), nil - } - case reflect.String: - var ( - s = rv.String() - isMinus = false - ) - if len(s) > 0 { - if s[0] == '-' { - isMinus = true - s = s[1:] - } else if s[0] == '+' { - s = s[1:] - } - } - // Hexadecimal. - if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { - if v, e := strconv.ParseInt(s[2:], 16, 64); e == nil { - if isMinus { - return -v, nil - } - return v, nil - } - } - // Decimal. - if v, e := strconv.ParseInt(s, 10, 64); e == nil { - if isMinus { - return -v, nil - } - return v, nil - } - // Float64. - valueInt64, err := doFloat64(s) - if err != nil { - return 0, err - } - if math.IsNaN(valueInt64) { - return 0, nil - } else { - if isMinus { - return -int64(valueInt64), nil - } - return int64(valueInt64), nil - } - default: - if f, ok := any.(localinterface.IInt64); ok { - return f.Int64(), nil - } - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport value type for converting to int64: %v`, - reflect.TypeOf(any), - ) -} diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go index 46809da6f13..04733e8b3e8 100644 --- a/util/gconv/gconv_map.go +++ b/util/gconv/gconv_map.go @@ -6,566 +6,42 @@ package gconv -import ( - "reflect" - "strings" - - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" - "github.com/gogf/gf/v2/util/gtag" -) - -type recursiveType string - -const ( - recursiveTypeAuto recursiveType = "auto" - recursiveTypeTrue recursiveType = "true" -) - -// MapOption specifies the option for map converting. -type MapOption struct { - // Deep marks doing Map function recursively, which means if the attribute of given converting value - // is also a struct/*struct, it automatically calls Map function on this attribute converting it to - // a map[string]interface{} type variable. - Deep bool - - // OmitEmpty ignores the attributes that has json `omitempty` tag. - OmitEmpty bool - - // Tags specifies the converted map key name by struct tag name. - Tags []string -} - -// Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a +// Map converts any variable `value` to map[string]any. If the parameter `value` is not a // map/struct/*struct type, then the conversion will fail and returns nil. // // If `value` is a struct/*struct object, the second parameter `priorityTagAndFieldName` specifies the most priority // priorityTagAndFieldName that will be detected, otherwise it detects the priorityTagAndFieldName in order of: // gconv, json, field name. -func Map(value interface{}, option ...MapOption) map[string]interface{} { - return doMapConvert(value, recursiveTypeAuto, false, option...) +func Map(value any, option ...MapOption) map[string]any { + result, _ := defaultConverter.Map(value, getUsedMapOption(option...)) + return result } // MapDeep does Map function recursively, which means if the attribute of `value` // is also a struct/*struct, calls Map function on this attribute converting it to -// a map[string]interface{} type variable. +// a map[string]any type variable. // Deprecated: used Map instead. -func MapDeep(value interface{}, tags ...string) map[string]interface{} { - return doMapConvert(value, recursiveTypeTrue, false, MapOption{ - Deep: true, - Tags: tags, +func MapDeep(value any, tags ...string) map[string]any { + result, _ := defaultConverter.Map(value, MapOption{ + Deep: true, + OmitEmpty: false, + Tags: tags, + ContinueOnError: true, }) -} - -// doMapConvert implements the map converting. -// It automatically checks and converts json string to map if `value` is string/[]byte. -// -// TODO completely implement the recursive converting for all types, especially the map. -func doMapConvert(value interface{}, recursive recursiveType, mustMapReturn bool, option ...MapOption) map[string]interface{} { - if value == nil { - return nil - } - // It redirects to its underlying value if it has implemented interface iVal. - if v, ok := value.(localinterface.IVal); ok { - value = v.Val() - } - var ( - usedOption = getUsedMapOption(option...) - newTags = gtag.StructTagPriority - ) - if usedOption.Deep { - recursive = recursiveTypeTrue - } - switch len(usedOption.Tags) { - case 0: - // No need handling. - case 1: - newTags = append(strings.Split(usedOption.Tags[0], ","), gtag.StructTagPriority...) - default: - newTags = append(usedOption.Tags, gtag.StructTagPriority...) - } - // Assert the common combination of types, and finally it uses reflection. - dataMap := make(map[string]interface{}) - switch r := value.(type) { - case string: - // If it is a JSON string, automatically unmarshal it! - if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { - return nil - } - } else { - return nil - } - case []byte: - // If it is a JSON string, automatically unmarshal it! - if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { - if err := json.UnmarshalUseNumber(r, &dataMap); err != nil { - return nil - } - } else { - return nil - } - case map[interface{}]interface{}: - recursiveOption := usedOption - recursiveOption.Tags = newTags - for k, v := range r { - dataMap[String(k)] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - case map[interface{}]string: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]int: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]uint: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]float32: - for k, v := range r { - dataMap[String(k)] = v - } - case map[interface{}]float64: - for k, v := range r { - dataMap[String(k)] = v - } - case map[string]bool: - for k, v := range r { - dataMap[k] = v - } - case map[string]int: - for k, v := range r { - dataMap[k] = v - } - case map[string]uint: - for k, v := range r { - dataMap[k] = v - } - case map[string]float32: - for k, v := range r { - dataMap[k] = v - } - case map[string]float64: - for k, v := range r { - dataMap[k] = v - } - case map[string]string: - for k, v := range r { - dataMap[k] = v - } - case map[string]interface{}: - if recursive == recursiveTypeTrue { - recursiveOption := usedOption - recursiveOption.Tags = newTags - // A copy of current map. - for k, v := range r { - dataMap[k] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - } else { - // It returns the map directly without any changing. - return r - } - case map[int]interface{}: - recursiveOption := usedOption - recursiveOption.Tags = newTags - for k, v := range r { - dataMap[String(k)] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: v, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - }, - ) - } - case map[int]string: - for k, v := range r { - dataMap[String(k)] = v - } - case map[uint]string: - for k, v := range r { - dataMap[String(k)] = v - } - - default: - // Not a common type, it then uses reflection for conversion. - var reflectValue reflect.Value - if v, ok := value.(reflect.Value); ok { - reflectValue = v - } else { - reflectValue = reflect.ValueOf(value) - } - reflectKind := reflectValue.Kind() - // If it is a pointer, we should find its real data type. - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - // If `value` is type of array, it converts the value of even number index as its key and - // the value of odd number index as its corresponding value, for example: - // []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"} - // []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil} - case reflect.Slice, reflect.Array: - length := reflectValue.Len() - for i := 0; i < length; i += 2 { - if i+1 < length { - dataMap[String(reflectValue.Index(i).Interface())] = reflectValue.Index(i + 1).Interface() - } else { - dataMap[String(reflectValue.Index(i).Interface())] = nil - } - } - case reflect.Map, reflect.Struct, reflect.Interface: - recursiveOption := usedOption - recursiveOption.Tags = newTags - convertedValue := doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: true, - Value: value, - RecursiveType: recursive, - RecursiveOption: recursive == recursiveTypeTrue, - Option: recursiveOption, - MustMapReturn: mustMapReturn, - }, - ) - if m, ok := convertedValue.(map[string]interface{}); ok { - return m - } - return nil - default: - return nil - } - } - return dataMap -} - -func getUsedMapOption(option ...MapOption) MapOption { - var usedOption MapOption - if len(option) > 0 { - usedOption = option[0] - } - return usedOption -} - -type doMapConvertForMapOrStructValueInput struct { - IsRoot bool // It returns directly if it is not root and with no recursive converting. - Value interface{} // Current operation value. - RecursiveType recursiveType // The type from top function entry. - RecursiveOption bool // Whether convert recursively for `current` operation. - Option MapOption // Map converting option. - MustMapReturn bool // Must return map instead of Value when empty. -} - -func doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) interface{} { - if !in.IsRoot && !in.RecursiveOption { - return in.Value - } - - var reflectValue reflect.Value - if v, ok := in.Value.(reflect.Value); ok { - reflectValue = v - in.Value = v.Interface() - } else { - reflectValue = reflect.ValueOf(in.Value) - } - reflectKind := reflectValue.Kind() - // If it is a pointer, we should find its real data type. - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Map: - var ( - mapIter = reflectValue.MapRange() - dataMap = make(map[string]interface{}) - ) - for mapIter.Next() { - var ( - mapKeyValue = mapIter.Value() - mapValue interface{} - ) - switch { - case mapKeyValue.IsZero(): - if utils.CanCallIsNil(mapKeyValue) && mapKeyValue.IsNil() { - // quick check for nil value. - mapValue = nil - } else { - // in case of: - // exception recovered: reflect: call of reflect.Value.Interface on zero Value - mapValue = reflect.New(mapKeyValue.Type()).Elem().Interface() - } - default: - mapValue = mapKeyValue.Interface() - } - dataMap[String(mapIter.Key().Interface())] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapValue, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - return dataMap - - case reflect.Struct: - var dataMap = make(map[string]interface{}) - // Map converting interface check. - if v, ok := in.Value.(localinterface.IMapStrAny); ok { - // Value copy, in case of concurrent safety. - for mapK, mapV := range v.MapStrAny() { - if in.RecursiveOption { - dataMap[mapK] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapV, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } else { - dataMap[mapK] = mapV - } - } - if len(dataMap) > 0 { - return dataMap - } - } - // Using reflect for converting. - var ( - rtField reflect.StructField - rvField reflect.Value - reflectType = reflectValue.Type() // attribute value type. - mapKey = "" // mapKey may be the tag name or the struct attribute name. - ) - for i := 0; i < reflectValue.NumField(); i++ { - rtField = reflectType.Field(i) - rvField = reflectValue.Field(i) - // Only convert the public attributes. - fieldName := rtField.Name - if !utils.IsLetterUpper(fieldName[0]) { - continue - } - mapKey = "" - fieldTag := rtField.Tag - for _, tag := range in.Option.Tags { - if mapKey = fieldTag.Get(tag); mapKey != "" { - break - } - } - if mapKey == "" { - mapKey = fieldName - } else { - // Support json tag feature: -, omitempty - mapKey = strings.TrimSpace(mapKey) - if mapKey == "-" { - continue - } - array := strings.Split(mapKey, ",") - if len(array) > 1 { - switch strings.TrimSpace(array[1]) { - case "omitempty": - if in.Option.OmitEmpty && empty.IsEmpty(rvField.Interface()) { - continue - } else { - mapKey = strings.TrimSpace(array[0]) - } - default: - mapKey = strings.TrimSpace(array[0]) - } - } - if mapKey == "" { - mapKey = fieldName - } - } - if in.RecursiveOption || rtField.Anonymous { - // Do map converting recursively. - var ( - rvAttrField = rvField - rvAttrKind = rvField.Kind() - ) - if rvAttrKind == reflect.Ptr { - rvAttrField = rvField.Elem() - rvAttrKind = rvAttrField.Kind() - } - switch rvAttrKind { - case reflect.Struct: - // Embedded struct and has no fields, just ignores it. - // Eg: gmeta.Meta - if rvAttrField.Type().NumField() == 0 { - continue - } - var ( - hasNoTag = mapKey == fieldName - // DO NOT use rvAttrField.Interface() here, - // as it might be changed from pointer to struct. - rvInterface = rvField.Interface() - ) - switch { - case hasNoTag && rtField.Anonymous: - // It means this attribute field has no tag. - // Overwrite the attribute with sub-struct attribute fields. - anonymousValue := doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: true, - Option: in.Option, - }) - if m, ok := anonymousValue.(map[string]interface{}); ok { - for k, v := range m { - dataMap[k] = v - } - } else { - dataMap[mapKey] = rvInterface - } - - // It means this attribute field has desired tag. - case !hasNoTag && rtField.Anonymous: - dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: true, - Option: in.Option, - }) - - default: - dataMap[mapKey] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvInterface, - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }) - } - - // The struct attribute is type of slice. - case reflect.Array, reflect.Slice: - length := rvAttrField.Len() - if length == 0 { - dataMap[mapKey] = rvAttrField.Interface() - break - } - array := make([]interface{}, length) - for arrayIndex := 0; arrayIndex < length; arrayIndex++ { - array[arrayIndex] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: rvAttrField.Index(arrayIndex).Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - dataMap[mapKey] = array - case reflect.Map: - var ( - mapIter = rvAttrField.MapRange() - nestedMap = make(map[string]interface{}) - ) - for mapIter.Next() { - nestedMap[String(mapIter.Key().Interface())] = doMapConvertForMapOrStructValue( - doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: mapIter.Value().Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }, - ) - } - dataMap[mapKey] = nestedMap - default: - if rvField.IsValid() { - dataMap[mapKey] = reflectValue.Field(i).Interface() - } else { - dataMap[mapKey] = nil - } - } - } else { - // No recursive map value converting - if rvField.IsValid() { - dataMap[mapKey] = reflectValue.Field(i).Interface() - } else { - dataMap[mapKey] = nil - } - } - } - if !in.MustMapReturn && len(dataMap) == 0 { - return in.Value - } - return dataMap - - // The given value is type of slice. - case reflect.Array, reflect.Slice: - length := reflectValue.Len() - if length == 0 { - break - } - array := make([]interface{}, reflectValue.Len()) - for i := 0; i < length; i++ { - array[i] = doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ - IsRoot: false, - Value: reflectValue.Index(i).Interface(), - RecursiveType: in.RecursiveType, - RecursiveOption: in.RecursiveType == recursiveTypeTrue, - Option: in.Option, - }) - } - return array - - default: - } - return in.Value + return result } // MapStrStr converts `value` to map[string]string. // Note that there might be data copy for this map type converting. -func MapStrStr(value interface{}, option ...MapOption) map[string]string { - if r, ok := value.(map[string]string); ok { - return r - } - m := Map(value, option...) - if len(m) > 0 { - vMap := make(map[string]string, len(m)) - for k, v := range m { - vMap[k] = String(v) - } - return vMap - } - return nil +func MapStrStr(value any, option ...MapOption) map[string]string { + result, _ := defaultConverter.MapStrStr(value, getUsedMapOption(option...)) + return result } // MapStrStrDeep converts `value` to map[string]string recursively. // Note that there might be data copy for this map type converting. // Deprecated: used MapStrStr instead. -func MapStrStrDeep(value interface{}, tags ...string) map[string]string { +func MapStrStrDeep(value any, tags ...string) map[string]string { if r, ok := value.(map[string]string); ok { return r } @@ -579,3 +55,13 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string { } return nil } + +func getUsedMapOption(option ...MapOption) MapOption { + var usedOption = MapOption{ + ContinueOnError: true, + } + if len(option) > 0 { + usedOption = option[0] + } + return usedOption +} diff --git a/util/gconv/gconv_maps.go b/util/gconv/gconv_maps.go index a68bb4e1046..b2d57499458 100644 --- a/util/gconv/gconv_maps.go +++ b/util/gconv/gconv_maps.go @@ -9,72 +9,42 @@ package gconv import "github.com/gogf/gf/v2/internal/json" // SliceMap is alias of Maps. -func SliceMap(any interface{}, option ...MapOption) []map[string]interface{} { +func SliceMap(any any, option ...MapOption) []map[string]any { return Maps(any, option...) } // SliceMapDeep is alias of MapsDeep. // Deprecated: used SliceMap instead. -func SliceMapDeep(any interface{}) []map[string]interface{} { +func SliceMapDeep(any any) []map[string]any { return MapsDeep(any) } -// Maps converts `value` to []map[string]interface{}. +// Maps converts `value` to []map[string]any. // Note that it automatically checks and converts json string to []map if `value` is string/[]byte. -func Maps(value interface{}, option ...MapOption) []map[string]interface{} { - if value == nil { - return nil +func Maps(value any, option ...MapOption) []map[string]any { + mapOption := MapOption{ + ContinueOnError: true, } - switch r := value.(type) { - case string: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []byte: - list := make([]map[string]interface{}, 0) - if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { - if err := json.UnmarshalUseNumber(r, &list); err != nil { - return nil - } - return list - } else { - return nil - } - - case []map[string]interface{}: - return r - - default: - array := Interfaces(value) - if len(array) == 0 { - return nil - } - list := make([]map[string]interface{}, len(array)) - for k, v := range array { - list[k] = Map(v, option...) - } - return list + if len(option) > 0 { + mapOption = option[0] } + result, _ := defaultConverter.SliceMap(value, SliceOption{ + ContinueOnError: true, + }, mapOption) + return result } -// MapsDeep converts `value` to []map[string]interface{} recursively. +// MapsDeep converts `value` to []map[string]any recursively. // // TODO completely implement the recursive converting for all types. // Deprecated: used Maps instead. -func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { +func MapsDeep(value any, tags ...string) []map[string]any { if value == nil { return nil } switch r := value.(type) { case string: - list := make([]map[string]interface{}, 0) + list := make([]map[string]any, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { return nil @@ -85,7 +55,7 @@ func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { } case []byte: - list := make([]map[string]interface{}, 0) + list := make([]map[string]any, 0) if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { if err := json.UnmarshalUseNumber(r, &list); err != nil { return nil @@ -95,8 +65,8 @@ func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { return nil } - case []map[string]interface{}: - list := make([]map[string]interface{}, len(r)) + case []map[string]any: + list := make([]map[string]any, len(r)) for k, v := range r { list[k] = MapDeep(v, tags...) } @@ -107,7 +77,7 @@ func MapsDeep(value interface{}, tags ...string) []map[string]interface{} { if len(array) == 0 { return nil } - list := make([]map[string]interface{}, len(array)) + list := make([]map[string]any, len(array)) for k, v := range array { list[k] = MapDeep(v, tags...) } diff --git a/util/gconv/gconv_maptomap.go b/util/gconv/gconv_maptomap.go index ffd352e582f..5986c332209 100644 --- a/util/gconv/gconv_maptomap.go +++ b/util/gconv/gconv_maptomap.go @@ -6,126 +6,9 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // 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 { +func MapToMap(params any, pointer any, mapping ...map[string]string) error { return Scan(params, pointer, mapping...) } - -// doMapToMap converts any map type variable `params` to another map type variable `pointer`. -// -// The parameter `params` can be any type of map, like: -// map[string]string, map[string]struct, map[string]*struct, reflect.Value, etc. -// -// The parameter `pointer` should be type of *map, like: -// map[int]string, map[string]struct, map[string]*struct, reflect.Value, etc. -// -// 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) { - var ( - paramsRv reflect.Value - paramsKind reflect.Kind - keyToAttributeNameMapping map[string]string - ) - if len(mapping) > 0 { - keyToAttributeNameMapping = mapping[0] - } - if v, ok := params.(reflect.Value); ok { - paramsRv = v - } else { - paramsRv = reflect.ValueOf(params) - } - paramsKind = paramsRv.Kind() - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Map { - return doMapToMap(Map(params), pointer, mapping...) - } - // Empty params map, no need continue. - if paramsRv.Len() == 0 { - return nil - } - var pointerRv reflect.Value - if v, ok := pointer.(reflect.Value); ok { - pointerRv = v - } else { - pointerRv = reflect.ValueOf(pointer) - } - pointerKind := pointerRv.Kind() - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Map { - 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. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - var ( - paramsKeys = paramsRv.MapKeys() - pointerKeyType = pointerRv.Type().Key() - pointerValueType = pointerRv.Type().Elem() - pointerValueKind = pointerValueType.Kind() - dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) - ) - // Retrieve the true element type of target map. - if pointerValueKind == reflect.Ptr { - pointerValueKind = pointerValueType.Elem().Kind() - } - for _, key := range paramsKeys { - mapValue := reflect.New(pointerValueType).Elem() - switch pointerValueKind { - case reflect.Map, reflect.Struct: - if err = doStruct( - paramsRv.MapIndex(key).Interface(), mapValue, keyToAttributeNameMapping, "", - ); err != nil { - return err - } - default: - mapValue.Set( - reflect.ValueOf( - doConvert(doConvertInput{ - FromValue: paramsRv.MapIndex(key).Interface(), - ToTypeName: pointerValueType.String(), - ReferValue: mapValue, - Extra: nil, - }), - ), - ) - } - var mapKey = reflect.ValueOf( - doConvert(doConvertInput{ - FromValue: key.Interface(), - ToTypeName: pointerKeyType.Name(), - ReferValue: reflect.New(pointerKeyType).Elem().Interface(), - Extra: nil, - }), - ) - dataMap.SetMapIndex(mapKey, mapValue) - } - pointerRv.Set(dataMap) - return nil -} diff --git a/util/gconv/gconv_maptomaps.go b/util/gconv/gconv_maptomaps.go index 330ed40d045..aaf9ae2743d 100644 --- a/util/gconv/gconv_maptomaps.go +++ b/util/gconv/gconv_maptomaps.go @@ -6,121 +6,8 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // 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 { +func MapToMaps(params any, pointer any, mapping ...map[string]string) error { return Scan(params, pointer, mapping...) } - -// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`. -// -// The parameter `params` can be type of []map, []*map, []struct, []*struct. -// -// The parameter `pointer` should be type of []map, []*map. -// -// 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) { - // Params and its element type check. - var ( - paramsRv reflect.Value - paramsKind reflect.Kind - ) - if v, ok := params.(reflect.Value); ok { - paramsRv = v - } else { - paramsRv = reflect.ValueOf(params) - } - paramsKind = paramsRv.Kind() - if paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - if paramsKind != reflect.Array && paramsKind != reflect.Slice { - return gerror.NewCode( - gcode.CodeInvalidParameter, - "params should be type of slice, example: []map/[]*map/[]struct/[]*struct", - ) - } - var ( - paramsElem = paramsRv.Type().Elem() - paramsElemKind = paramsElem.Kind() - ) - if paramsElemKind == reflect.Ptr { - 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, - ) - } - // Empty slice, no need continue. - if paramsRv.Len() == 0 { - return nil - } - // Pointer and its element type check. - var ( - pointerRv = reflect.ValueOf(pointer) - pointerKind = pointerRv.Kind() - ) - for pointerKind == reflect.Ptr { - pointerRv = pointerRv.Elem() - pointerKind = pointerRv.Kind() - } - if pointerKind != reflect.Array && pointerKind != reflect.Slice { - return gerror.NewCode(gcode.CodeInvalidParameter, "pointer should be type of *[]map/*[]*map") - } - var ( - pointerElemType = pointerRv.Type().Elem() - pointerElemKind = pointerElemType.Kind() - ) - if pointerElemKind == reflect.Ptr { - pointerElemKind = pointerElemType.Elem().Kind() - } - if pointerElemKind != reflect.Map { - return gerror.NewCode(gcode.CodeInvalidParameter, "pointer element should be type of map/*map") - } - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - var ( - pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len()) - ) - for i := 0; i < paramsRv.Len(); i++ { - var item reflect.Value - if pointerElemType.Kind() == reflect.Ptr { - item = reflect.New(pointerElemType.Elem()) - if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { - return err - } - pointerSlice.Index(i).Set(item) - } else { - item = reflect.New(pointerElemType) - if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil { - return err - } - pointerSlice.Index(i).Set(item.Elem()) - } - } - pointerRv.Set(pointerSlice) - return -} diff --git a/util/gconv/gconv_scan.go b/util/gconv/gconv_scan.go index 376a2b1aae2..8d872420863 100644 --- a/util/gconv/gconv_scan.go +++ b/util/gconv/gconv_scan.go @@ -6,15 +6,6 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Scan automatically checks the type of `pointer` and converts `params` to `pointer`. // It supports various types of parameter conversions, including: // 1. Basic types (int, string, float, etc.) @@ -26,321 +17,11 @@ import ( // The `paramKeyToAttrMap` parameter is used for mapping between attribute names and parameter keys. // TODO: change `paramKeyToAttrMap` to `ScanOption` to be more scalable; add `DeepCopy` option for `ScanOption`. func Scan(srcValue any, dstPointer any, paramKeyToAttrMap ...map[string]string) (err error) { - // Check if srcValue is nil, in which case no conversion is needed - if srcValue == nil { - return nil - } - // Check if dstPointer is nil, which is an invalid parameter - if dstPointer == nil { - return gerror.NewCode( - gcode.CodeInvalidParameter, - `destination pointer should not be nil`, - ) - } - - // Get the reflection type and value of dstPointer - var ( - 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) - } - - // Validate the kind of dstPointer - var dstPointerReflectKind = dstPointerReflectType.Kind() - if dstPointerReflectKind != reflect.Ptr { - // If dstPointer is not a pointer, try to get its address - if dstPointerReflectValue.CanAddr() { - dstPointerReflectValue = dstPointerReflectValue.Addr() - dstPointerReflectType = dstPointerReflectValue.Type() - dstPointerReflectKind = dstPointerReflectType.Kind() - } else { - // If dstPointer is not a pointer and cannot be addressed, return an error - return gerror.NewCodef( - gcode.CodeInvalidParameter, - `destination pointer should be type of pointer, but got type: %v`, - dstPointerReflectType, - ) - } - } - - // Get the reflection value of srcValue - var srcValueReflectValue reflect.Value - if v, ok := srcValue.(reflect.Value); ok { - srcValueReflectValue = v - } else { - srcValueReflectValue = reflect.ValueOf(srcValue) - } - - // Get the element type and kind of dstPointer - var ( - dstPointerReflectValueElem = dstPointerReflectValue.Elem() - dstPointerReflectValueElemKind = dstPointerReflectValueElem.Kind() - ) - // Handle multiple level pointers - if dstPointerReflectValueElemKind == reflect.Ptr { - if dstPointerReflectValueElem.IsNil() { - // Create a new value for the pointer dereference - nextLevelPtr := reflect.New(dstPointerReflectValueElem.Type().Elem()) - // Recursively scan into the dereferenced pointer - if err = Scan(srcValueReflectValue, nextLevelPtr, paramKeyToAttrMap...); err == nil { - dstPointerReflectValueElem.Set(nextLevelPtr) - } - return - } - return Scan(srcValueReflectValue, dstPointerReflectValueElem, paramKeyToAttrMap...) - } - - // Check if srcValue and dstPointer are the same type, in which case direct assignment can be performed - if ok := doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok { - return nil - } - - // Handle different destination types - switch dstPointerReflectValueElemKind { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - // Convert to int type - dstPointerReflectValueElem.SetInt(Int64(srcValue)) - return nil - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - // Convert to uint type - dstPointerReflectValueElem.SetUint(Uint64(srcValue)) - return nil - - case reflect.Float32, reflect.Float64: - // Convert to float type - dstPointerReflectValueElem.SetFloat(Float64(srcValue)) - return nil - - case reflect.String: - // Convert to string type - dstPointerReflectValueElem.SetString(String(srcValue)) - return nil - - case reflect.Bool: - // Convert to bool type - dstPointerReflectValueElem.SetBool(Bool(srcValue)) - return nil - - case reflect.Slice: - // Handle slice type conversion - var ( - dstElemType = dstPointerReflectValueElem.Type().Elem() - dstElemKind = dstElemType.Kind() - ) - // The slice element might be a pointer type - if dstElemKind == reflect.Ptr { - dstElemType = dstElemType.Elem() - dstElemKind = dstElemType.Kind() - } - // Special handling for struct or map slice elements - if dstElemKind == reflect.Struct || dstElemKind == reflect.Map { - return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - } - // Handle basic type slice conversions - var srcValueReflectValueKind = srcValueReflectValue.Kind() - if srcValueReflectValueKind == reflect.Slice || srcValueReflectValueKind == reflect.Array { - var ( - srcLen = srcValueReflectValue.Len() - newSlice = reflect.MakeSlice(dstPointerReflectValueElem.Type(), srcLen, srcLen) - ) - for i := 0; i < srcLen; i++ { - srcElem := srcValueReflectValue.Index(i).Interface() - switch dstElemType.Kind() { - case reflect.String: - newSlice.Index(i).SetString(String(srcElem)) - case reflect.Int: - newSlice.Index(i).SetInt(Int64(srcElem)) - case reflect.Int64: - newSlice.Index(i).SetInt(Int64(srcElem)) - case reflect.Float64: - newSlice.Index(i).SetFloat(Float64(srcElem)) - case reflect.Bool: - newSlice.Index(i).SetBool(Bool(srcElem)) - default: - return Scan( - srcElem, newSlice.Index(i).Addr().Interface(), paramKeyToAttrMap..., - ) - } - } - dstPointerReflectValueElem.Set(newSlice) - return nil - } - return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - - default: - // Handle complex types (structs, maps, etc.) - return doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, paramKeyToAttrMap...) - } -} - -// doScanForComplicatedTypes handles the scanning of complex data types. -// It supports converting between maps, structs, and slices of these types. -// The function first attempts JSON conversion, then falls back to specific type handling. -// -// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting. -// -// Parameters: -// - srcValue: The source value to convert from -// - dstPointer: The destination pointer to convert to -// - dstPointerReflectType: The reflection type of the destination pointer -// - paramKeyToAttrMap: Optional mapping between parameter keys and struct attribute names -func doScanForComplicatedTypes( - srcValue, dstPointer any, - dstPointerReflectType reflect.Type, - paramKeyToAttrMap ...map[string]string, -) error { - // Try JSON conversion first - ok, err := doConvertWithJsonCheck(srcValue, dstPointer) - if err != nil { - return err + option := ScanOption{ + ContinueOnError: true, } - if ok { - return nil - } - - // Handle specific type conversions - var ( - dstPointerReflectTypeElem = dstPointerReflectType.Elem() - dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind() - keyToAttributeNameMapping map[string]string - ) if len(paramKeyToAttrMap) > 0 { - keyToAttributeNameMapping = paramKeyToAttrMap[0] - } - - // Handle different destination types - switch dstPointerReflectTypeElemKind { - case reflect.Map: - // Convert map to map - return doMapToMap(srcValue, dstPointer, paramKeyToAttrMap...) - - case reflect.Array, reflect.Slice: - var ( - sliceElem = dstPointerReflectTypeElem.Elem() - sliceElemKind = sliceElem.Kind() - ) - // Handle pointer elements - for sliceElemKind == reflect.Ptr { - sliceElem = sliceElem.Elem() - sliceElemKind = sliceElem.Kind() - } - if sliceElemKind == reflect.Map { - // Convert to slice of maps - return doMapToMaps(srcValue, dstPointer, paramKeyToAttrMap...) - } - // Convert to slice of structs - return doStructs(srcValue, dstPointer, keyToAttributeNameMapping, "") - - default: - // Convert to single struct - return doStruct(srcValue, dstPointer, keyToAttributeNameMapping, "") - } -} - -// doConvertWithTypeCheck supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` -// for converting. -func doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) { - if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() { - return false - } - switch { - // Examples: - // UploadFile => UploadFile - // []UploadFile => []UploadFile - // *UploadFile => *UploadFile - // *[]UploadFile => *[]UploadFile - // map[int][int] => map[int][int] - // []map[int][int] => []map[int][int] - // *[]map[int][int] => *[]map[int][int] - case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type(): - dstPointerReflectValueElem.Set(srcValueReflectValue) - return true - - // Examples: - // UploadFile => *UploadFile - // []UploadFile => *[]UploadFile - // map[int][int] => *map[int][int] - // []map[int][int] => *[]map[int][int] - case dstPointerReflectValueElem.Kind() == reflect.Ptr && - dstPointerReflectValueElem.Elem().IsValid() && - dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type(): - dstPointerReflectValueElem.Elem().Set(srcValueReflectValue) - return true - - // Examples: - // *UploadFile => UploadFile - // *[]UploadFile => []UploadFile - // *map[int][int] => map[int][int] - // *[]map[int][int] => []map[int][int] - case srcValueReflectValue.Kind() == reflect.Ptr && - srcValueReflectValue.Elem().IsValid() && - dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type(): - dstPointerReflectValueElem.Set(srcValueReflectValue.Elem()) - return true - - default: - return false - } -} - -// doConvertWithJsonCheck attempts to convert the source value to the destination -// using JSON marshaling and unmarshaling. This is particularly useful for complex -// types that can be represented as JSON. -// -// Parameters: -// - srcValue: The source value to convert from -// - dstPointer: The destination pointer to convert to -// -// Returns: -// - bool: true if JSON conversion was successful -// - error: any error that occurred during conversion -func doConvertWithJsonCheck(srcValue any, dstPointer any) (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 - } - return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface()) - } else if dstPointerReflectType.CanAddr() { - return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface()) - } - } else { - return true, json.UnmarshalUseNumber(valueResult, dstPointer) - } - } - - 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 - } - return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface()) - } else if dstPointerReflectType.CanAddr() { - return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface()) - } - } else { - return true, json.UnmarshalUseNumber(valueBytes, dstPointer) - } - } - - default: - // The `params` might be struct that implements interface function Interface, eg: gvar.Var. - if v, ok := srcValue.(localinterface.IInterface); ok { - return doConvertWithJsonCheck(v.Interface(), dstPointer) - } + option.ParamKeyToAttrMap = paramKeyToAttrMap[0] } - return false, nil + return defaultConverter.Scan(srcValue, dstPointer, option) } diff --git a/util/gconv/gconv_scan_list.go b/util/gconv/gconv_scan_list.go index 8fae2265ce1..78cc98335b5 100644 --- a/util/gconv/gconv_scan_list.go +++ b/util/gconv/gconv_scan_list.go @@ -93,7 +93,9 @@ import ( // 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) { +func ScanList( + structSlice any, structSlicePointer any, bindToAttrName string, relationAttrNameAndFields ...string, +) (err error) { var ( relationAttrName string relationFields string @@ -111,7 +113,7 @@ func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAtt // 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, + structSlice any, structSlicePointer any, bindToAttrName, relationAttrName, relationFields string, ) (err error) { var ( maps = Maps(structSlice) @@ -169,7 +171,7 @@ func doScanList( // Relation variables. var ( - relationDataMap map[string]interface{} + relationDataMap map[string]any relationFromFieldName string // Eg: relationKV: id:uid -> id relationBindToFieldName string // Eg: relationKV: id:uid -> uid ) @@ -315,7 +317,7 @@ func doScanList( relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName) if relationFromAttrField.IsValid() { // results := make(Result, 0) - results := make([]interface{}, 0) + results := make([]any, 0) for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) { item := v results = append(results, item) diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go index 00ecee0c224..8506f25428d 100644 --- a/util/gconv/gconv_slice_any.go +++ b/util/gconv/gconv_slice_any.go @@ -6,14 +6,6 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // SliceAny is alias of Interfaces. func SliceAny(any interface{}) []interface{} { return Interfaces(any) @@ -21,111 +13,8 @@ func SliceAny(any interface{}) []interface{} { // Interfaces converts `any` to []interface{}. func Interfaces(any interface{}) []interface{} { - if any == nil { - return nil - } - var array []interface{} - switch value := any.(type) { - case []interface{}: - array = value - case []string: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []int: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []int8: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []int16: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []int32: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []int64: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []uint: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - } - case []uint16: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []uint32: - for _, v := range value { - array = append(array, v) - } - case []uint64: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []bool: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []float32: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - case []float64: - array = make([]interface{}, len(value)) - for k, v := range value { - array[k] = v - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IInterfaces); ok { - return v.Interfaces() - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]interface{}, length) - ) - for i := 0; i < length; i++ { - slice[i] = originValueAndKind.OriginValue.Index(i).Interface() - } - return slice - - default: - return []interface{}{any} - } + result, _ := defaultConverter.SliceAny(any, SliceOption{ + ContinueOnError: true, + }) + return result } diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go index bca5af6b04b..32757782f79 100644 --- a/util/gconv/gconv_slice_float.go +++ b/util/gconv/gconv_slice_float.go @@ -6,14 +6,6 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // SliceFloat is alias of Floats. func SliceFloat(any interface{}) []float64 { return Floats(any) @@ -36,248 +28,16 @@ func Floats(any interface{}) []float64 { // Float32s converts `any` to []float32. func Float32s(any interface{}) []float32 { - if any == nil { - return nil - } - var ( - array []float32 = nil - ) - switch value := any.(type) { - case string: - if value == "" { - return []float32{} - } - return []float32{Float32(value)} - case []string: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []int: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []int8: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []int16: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []int32: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []int64: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []uint: - for _, v := range value { - array = append(array, Float32(v)) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - } - case []uint16: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []uint32: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []uint64: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []bool: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []float32: - array = value - case []float64: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - case []interface{}: - array = make([]float32, len(value)) - for k, v := range value { - array[k] = Float32(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IFloats); ok { - return Float32s(v.Floats()) - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Float32s(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]float32, length) - ) - for i := 0; i < length; i++ { - slice[i] = Float32(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []float32{} - } - return []float32{Float32(any)} - } + result, _ := defaultConverter.SliceFloat32(any, SliceOption{ + ContinueOnError: true, + }) + return result } // Float64s converts `any` to []float64. func Float64s(any interface{}) []float64 { - if any == nil { - return nil - } - var ( - array []float64 = nil - ) - switch value := any.(type) { - case string: - if value == "" { - return []float64{} - } - return []float64{Float64(value)} - case []string: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []int: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []int8: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []int16: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []int32: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []int64: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []uint: - for _, v := range value { - array = append(array, Float64(v)) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - } - case []uint16: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []uint32: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []uint64: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []bool: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []float32: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - case []float64: - array = value - case []interface{}: - array = make([]float64, len(value)) - for k, v := range value { - array[k] = Float64(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IFloats); ok { - return v.Floats() - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Floats(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]float64, length) - ) - for i := 0; i < length; i++ { - slice[i] = Float64(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []float64{} - } - return []float64{Float64(any)} - } + result, _ := defaultConverter.SliceFloat64(any, SliceOption{ + ContinueOnError: true, + }) + return result } diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go index 929acfa147e..dedc634d115 100644 --- a/util/gconv/gconv_slice_int.go +++ b/util/gconv/gconv_slice_int.go @@ -6,412 +6,41 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // SliceInt is alias of Ints. -func SliceInt(any interface{}) []int { +func SliceInt(any any) []int { return Ints(any) } // SliceInt32 is alias of Int32s. -func SliceInt32(any interface{}) []int32 { +func SliceInt32(any any) []int32 { return Int32s(any) } // SliceInt64 is alias of Int64s. -func SliceInt64(any interface{}) []int64 { +func SliceInt64(any any) []int64 { return Int64s(any) } // Ints converts `any` to []int. -func Ints(any interface{}) []int { - if any == nil { - return nil - } - var ( - array []int = nil - ) - switch value := any.(type) { - case []string: - array = make([]int, len(value)) - for k, v := range value { - array[k] = Int(v) - } - case []int: - array = value - case []int8: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []int16: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []int32: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []int64: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []uint: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - } - case []uint16: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []uint32: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []uint64: - array = make([]int, len(value)) - for k, v := range value { - array[k] = int(v) - } - case []bool: - array = make([]int, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]int, len(value)) - for k, v := range value { - array[k] = Int(v) - } - case []float64: - array = make([]int, len(value)) - for k, v := range value { - array[k] = Int(v) - } - case []interface{}: - array = make([]int, len(value)) - for k, v := range value { - array[k] = Int(v) - } - case [][]byte: - array = make([]int, len(value)) - for k, v := range value { - array[k] = Int(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IInts); ok { - return v.Ints() - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Ints(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]int, length) - ) - for i := 0; i < length; i++ { - slice[i] = Int(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []int{} - } - return []int{Int(any)} - } +func Ints(any any) []int { + result, _ := defaultConverter.SliceInt(any, SliceOption{ + ContinueOnError: true, + }) + return result } // Int32s converts `any` to []int32. -func Int32s(any interface{}) []int32 { - if any == nil { - return nil - } - var ( - array []int32 = nil - ) - switch value := any.(type) { - case []string: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = Int32(v) - } - case []int: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []int8: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []int16: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []int32: - array = value - case []int64: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []uint: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - } - case []uint16: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []uint32: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []uint64: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = int32(v) - } - case []bool: - array = make([]int32, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = Int32(v) - } - case []float64: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = Int32(v) - } - case []interface{}: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = Int32(v) - } - case [][]byte: - array = make([]int32, len(value)) - for k, v := range value { - array[k] = Int32(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IInts); ok { - return Int32s(v.Ints()) - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Int32s(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]int32, length) - ) - for i := 0; i < length; i++ { - slice[i] = Int32(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []int32{} - } - return []int32{Int32(any)} - } +func Int32s(any any) []int32 { + result, _ := defaultConverter.SliceInt32(any, SliceOption{ + ContinueOnError: true, + }) + return result } // Int64s converts `any` to []int64. -func Int64s(any interface{}) []int64 { - if any == nil { - return nil - } - var ( - array []int64 = nil - ) - switch value := any.(type) { - case []string: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = Int64(v) - } - case []int: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []int8: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []int16: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []int32: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []int64: - array = value - case []uint: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - } - case []uint16: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []uint32: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []uint64: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = int64(v) - } - case []bool: - array = make([]int64, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = Int64(v) - } - case []float64: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = Int64(v) - } - case []interface{}: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = Int64(v) - } - case [][]byte: - array = make([]int64, len(value)) - for k, v := range value { - array[k] = Int64(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IInts); ok { - return Int64s(v.Ints()) - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Int64s(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]int64, length) - ) - for i := 0; i < length; i++ { - slice[i] = Int64(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []int64{} - } - return []int64{Int64(any)} - } +func Int64s(any any) []int64 { + result, _ := defaultConverter.SliceInt64(any, SliceOption{ + ContinueOnError: true, + }) + return result } diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go index 405e72ca522..b475dfb0836 100644 --- a/util/gconv/gconv_slice_str.go +++ b/util/gconv/gconv_slice_str.go @@ -6,14 +6,6 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // SliceStr is alias of Strings. func SliceStr(any interface{}) []string { return Strings(any) @@ -21,140 +13,8 @@ func SliceStr(any interface{}) []string { // Strings converts `any` to []string. func Strings(any interface{}) []string { - if any == nil { - return nil - } - var ( - array []string = nil - ) - switch value := any.(type) { - case []int: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []int8: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []int16: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []int32: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []int64: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []uint: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } - if array == nil { - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - return array - } - case string: - byteValue := []byte(value) - if json.Valid(byteValue) { - _ = json.UnmarshalUseNumber(byteValue, &array) - } - if array == nil { - if value == "" { - return []string{} - } - // Prevent strings from being null - // See Issue 3465 for details - return []string{value} - } - case []uint16: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []uint32: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []uint64: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []bool: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []float32: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []float64: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []interface{}: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - case []string: - array = value - case [][]byte: - array = make([]string, len(value)) - for k, v := range value { - array[k] = String(v) - } - } - if array != nil { - return array - } - if v, ok := any.(localinterface.IStrings); ok { - return v.Strings() - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Strings(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]string, length) - ) - for i := 0; i < length; i++ { - slice[i] = String(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []string{} - } - return []string{String(any)} - } + result, _ := defaultConverter.SliceStr(any, SliceOption{ + ContinueOnError: true, + }) + return result } diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go index 8ab15dbf7fa..7ab9b4fdf22 100644 --- a/util/gconv/gconv_slice_uint.go +++ b/util/gconv/gconv_slice_uint.go @@ -6,16 +6,6 @@ package gconv -import ( - "reflect" - "strings" - - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/reflection" - "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // SliceUint is alias of Uints. func SliceUint(any interface{}) []uint { return Uints(any) @@ -33,405 +23,24 @@ func SliceUint64(any interface{}) []uint64 { // Uints converts `any` to []uint. func Uints(any interface{}) []uint { - if any == nil { - return nil - } - - var ( - array []uint = nil - ) - switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint{} - } - if utils.IsNumeric(value) { - return []uint{Uint(value)} - } - - case []string: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = Uint(v) - } - case []int8: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []int16: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []int32: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []int64: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []uint: - array = value - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - } - case []uint16: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []uint32: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []uint64: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = uint(v) - } - case []bool: - array = make([]uint, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = Uint(v) - } - case []float64: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = Uint(v) - } - case []interface{}: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = Uint(v) - } - case [][]byte: - array = make([]uint, len(value)) - for k, v := range value { - array[k] = Uint(v) - } - } - - if array != nil { - return array - } - - // Default handler. - if v, ok := any.(localinterface.IUints); ok { - return v.Uints() - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Uints(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]uint, length) - ) - for i := 0; i < length; i++ { - slice[i] = Uint(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []uint{} - } - return []uint{Uint(any)} - } + result, _ := defaultConverter.SliceUint(any, SliceOption{ + ContinueOnError: true, + }) + return result } // Uint32s converts `any` to []uint32. func Uint32s(any interface{}) []uint32 { - if any == nil { - return nil - } - var ( - array []uint32 = nil - ) - switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint32{} - } - if utils.IsNumeric(value) { - return []uint32{Uint32(value)} - } - case []string: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = Uint32(v) - } - case []int8: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []int16: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []int32: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []int64: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []uint: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - } - case []uint16: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []uint32: - array = value - case []uint64: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = uint32(v) - } - case []bool: - array = make([]uint32, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = Uint32(v) - } - case []float64: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = Uint32(v) - } - case []interface{}: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = Uint32(v) - } - case [][]byte: - array = make([]uint32, len(value)) - for k, v := range value { - array[k] = Uint32(v) - } - } - if array != nil { - return array - } - - // Default handler. - if v, ok := any.(localinterface.IUints); ok { - return Uint32s(v.Uints()) - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Uint32s(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]uint32, length) - ) - for i := 0; i < length; i++ { - slice[i] = Uint32(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []uint32{} - } - return []uint32{Uint32(any)} - } + result, _ := defaultConverter.SliceUint32(any, SliceOption{ + ContinueOnError: true, + }) + return result } // Uint64s converts `any` to []uint64. func Uint64s(any interface{}) []uint64 { - if any == nil { - return nil - } - var ( - array []uint64 = nil - ) - switch value := any.(type) { - case string: - value = strings.TrimSpace(value) - if value == "" { - return []uint64{} - } - if utils.IsNumeric(value) { - return []uint64{Uint64(value)} - } - - case []string: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = Uint64(v) - } - case []int8: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []int16: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []int32: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []int64: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []uint: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []uint8: - if json.Valid(value) { - _ = json.UnmarshalUseNumber(value, &array) - } else { - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - } - case []uint16: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []uint32: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = uint64(v) - } - case []uint64: - array = value - case []bool: - array = make([]uint64, len(value)) - for k, v := range value { - if v { - array[k] = 1 - } else { - array[k] = 0 - } - } - case []float32: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = Uint64(v) - } - case []float64: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = Uint64(v) - } - case []interface{}: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = Uint64(v) - } - case [][]byte: - array = make([]uint64, len(value)) - for k, v := range value { - array[k] = Uint64(v) - } - } - if array != nil { - return array - } - // Default handler. - if v, ok := any.(localinterface.IUints); ok { - return Uint64s(v.Uints()) - } - if v, ok := any.(localinterface.IInterfaces); ok { - return Uint64s(v.Interfaces()) - } - // JSON format string value converting. - if checkJsonAndUnmarshalUseNumber(any, &array) { - return array - } - // Not a common type, it then uses reflection for conversion. - originValueAndKind := reflection.OriginValueAndKind(any) - switch originValueAndKind.OriginKind { - case reflect.Slice, reflect.Array: - var ( - length = originValueAndKind.OriginValue.Len() - slice = make([]uint64, length) - ) - for i := 0; i < length; i++ { - slice[i] = Uint64(originValueAndKind.OriginValue.Index(i).Interface()) - } - return slice - - default: - if originValueAndKind.OriginValue.IsZero() { - return []uint64{} - } - return []uint64{Uint64(any)} - } + result, _ := defaultConverter.SliceUint64(any, SliceOption{ + ContinueOnError: true, + }) + return result } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index cbb6eb0b0cf..e20b899581d 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -6,19 +6,6 @@ package gconv -import ( - "reflect" - "strings" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" - "github.com/gogf/gf/v2/util/gconv/internal/structcache" -) - // Struct maps the params key-value pairs to the corresponding struct object's attributes. // The third parameter `mapping` is unnecessary, indicating the mapping rules between the // custom key name and the attribute name(case-sensitive). @@ -32,609 +19,17 @@ import ( // It will automatically convert the first letter of the key to uppercase // in mapping procedure to do the matching. // It ignores the map key, if it does not match. -func Struct(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) { +func Struct(params any, pointer any, paramKeyToAttrMap ...map[string]string) (err error) { return Scan(params, pointer, paramKeyToAttrMap...) } // StructTag acts as Struct but also with support for priority tag feature, which retrieves the // specified priorityTagAndFieldName for `params` key-value items to struct attribute names mapping. // The parameter `priorityTag` supports multiple priorityTagAndFieldName that can be joined with char ','. -func StructTag(params interface{}, pointer interface{}, priorityTag string) (err error) { - return doStruct(params, pointer, nil, priorityTag) -} - -// 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) { - if params == nil { - // If `params` is nil, no conversion. - return nil - } - if pointer == nil { - 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 { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - - var ( - paramsReflectValue reflect.Value - paramsInterface interface{} // DO NOT use `params` directly as it might be type `reflect.Value` - pointerReflectValue reflect.Value - pointerReflectKind reflect.Kind - pointerElemReflectValue reflect.Value // The reflection value to struct element. - ) - if v, ok := params.(reflect.Value); ok { - paramsReflectValue = v - } else { - paramsReflectValue = reflect.ValueOf(params) - } - paramsInterface = paramsReflectValue.Interface() - if v, ok := pointer.(reflect.Value); ok { - pointerReflectValue = v - pointerElemReflectValue = v - } else { - pointerReflectValue = reflect.ValueOf(pointer) - pointerReflectKind = pointerReflectValue.Kind() - if pointerReflectKind != reflect.Ptr { - return gerror.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, - "destination pointer cannot be nil", - ) - } - pointerElemReflectValue = pointerReflectValue.Elem() - } - - // 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 - } - - // Normal unmarshalling interfaces checks. - if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok { - return err - } - - // It automatically creates struct object if necessary. - // For example, if `pointer` is **User, then `elem` is *User, which is a pointer to User. - if pointerElemReflectValue.Kind() == reflect.Ptr { - if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() { - e := reflect.New(pointerElemReflectValue.Type().Elem()) - pointerElemReflectValue.Set(e) - defer func() { - if err != nil { - // If it is converted failed, it reset the `pointer` to nil. - pointerReflectValue.Elem().Set(reflect.Zero(pointerReflectValue.Type().Elem())) - } - }() - } - // if v, ok := pointerElemReflectValue.Interface().(localinterface.IUnmarshalValue); ok { - // return v.UnmarshalValue(params) - // } - // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. - if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { - return err - } - // Retrieve its element, may be struct at last. - pointerElemReflectValue = pointerElemReflectValue.Elem() - } - paramsMap, ok := paramsInterface.(map[string]interface{}) - if !ok { - // paramsMap is the map[string]interface{} type variable for params. - // DO NOT use MapDeep here. - paramsMap = doMapConvert(paramsInterface, recursiveTypeAuto, true) - if paramsMap == nil { - return gerror.NewCodef( - gcode.CodeInvalidParameter, - `convert params from "%#v" to "map[string]interface{}" failed`, - params, - ) - } - } - // Nothing to be done as the parameters are empty. - if len(paramsMap) == 0 { - return nil - } - // Get struct info from cache or parse struct and cache the struct info. - cachedStructInfo := structcache.GetCachedStructInfo( - pointerElemReflectValue.Type(), priorityTag, - ) - // Nothing to be converted. - if cachedStructInfo == nil { - return nil - } - // For the structure types of 0 tagOrFiledNameToFieldInfoMap, - // they also need to be cached to prevent invalid logic - if cachedStructInfo.HasNoFields() { - return nil - } - var ( - // Indicates that those values have been used and cannot be reused. - usedParamsKeyOrTagNameMap = structcache.GetUsedParamsKeyOrTagNameMapFromPool() - cachedFieldInfo *structcache.CachedFieldInfo - paramsValue interface{} - ) - defer structcache.PutUsedParamsKeyOrTagNameMapToPool(usedParamsKeyOrTagNameMap) - - // Firstly, search according to custom mapping rules. - // If a possible direct assignment is found, reduce the number of subsequent map searches. - for paramKey, fieldName := range paramKeyToAttrMap { - paramsValue, ok = paramsMap[paramKey] - if !ok { - continue - } - cachedFieldInfo = cachedStructInfo.GetFieldInfo(fieldName) - if cachedFieldInfo != nil { - fieldValue := cachedFieldInfo.GetFieldReflectValueFrom(pointerElemReflectValue) - if err = bindVarToStructField( - fieldValue, - paramsValue, - cachedFieldInfo, - paramKeyToAttrMap, - ); err != nil { - return err - } - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cachedFieldInfo, paramsValue, pointerReflectValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - usedParamsKeyOrTagNameMap[paramKey] = struct{}{} - } - } - // Already done converting for given `paramsMap`. - if len(usedParamsKeyOrTagNameMap) == len(paramsMap) { - return nil - } - return bindStructWithLoopFieldInfos( - paramsMap, pointerElemReflectValue, paramKeyToAttrMap, usedParamsKeyOrTagNameMap, cachedStructInfo, - ) -} - -func setOtherSameNameField( - cachedFieldInfo *structcache.CachedFieldInfo, - srcValue any, - structValue reflect.Value, - paramKeyToAttrMap map[string]string, -) (err error) { - // loop the same field name of all sub attributes. - for _, otherFieldInfo := range cachedFieldInfo.OtherSameNameField { - fieldValue := cachedFieldInfo.GetOtherFieldReflectValueFrom(structValue, otherFieldInfo.FieldIndexes) - if err = bindVarToStructField(fieldValue, srcValue, otherFieldInfo, paramKeyToAttrMap); err != nil { - return err - } - } - return nil -} - -func bindStructWithLoopFieldInfos( - paramsMap map[string]any, - structValue reflect.Value, - paramKeyToAttrMap map[string]string, - usedParamsKeyOrTagNameMap map[string]struct{}, - cachedStructInfo *structcache.CachedStructInfo, -) (err error) { - var ( - cachedFieldInfo *structcache.CachedFieldInfo - fuzzLastKey string - fieldValue reflect.Value - paramKey string - paramValue any - matched bool - ok bool - ) - for _, cachedFieldInfo = range cachedStructInfo.FieldConvertInfos { - for _, fieldTag := range cachedFieldInfo.PriorityTagAndFieldName { - if paramValue, ok = paramsMap[fieldTag]; !ok { - continue - } - fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) - if err = bindVarToStructField( - fieldValue, paramValue, cachedFieldInfo, paramKeyToAttrMap, - ); err != nil { - return err - } - // handle same field name in nested struct. - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - usedParamsKeyOrTagNameMap[fieldTag] = struct{}{} - matched = true - break - } - if matched { - matched = false - continue - } - - fuzzLastKey = cachedFieldInfo.LastFuzzyKey.Load().(string) - if paramValue, ok = paramsMap[fuzzLastKey]; !ok { - paramKey, paramValue = fuzzyMatchingFieldName( - cachedFieldInfo.RemoveSymbolsFieldName, paramsMap, usedParamsKeyOrTagNameMap, - ) - ok = paramKey != "" - cachedFieldInfo.LastFuzzyKey.Store(paramKey) - } - if ok { - fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) - if paramValue != nil { - if err = bindVarToStructField( - fieldValue, paramValue, cachedFieldInfo, paramKeyToAttrMap, - ); err != nil { - return err - } - // handle same field name in nested struct. - if len(cachedFieldInfo.OtherSameNameField) > 0 { - if err = setOtherSameNameField( - cachedFieldInfo, paramValue, structValue, paramKeyToAttrMap, - ); err != nil { - return err - } - } - } - usedParamsKeyOrTagNameMap[paramKey] = struct{}{} - } - } - return nil -} - -// fuzzy matching rule: -// to match field name and param key in case-insensitive and without symbols. -func fuzzyMatchingFieldName( - fieldName string, - paramsMap map[string]any, - usedParamsKeyMap map[string]struct{}, -) (string, any) { - for paramKey, paramValue := range paramsMap { - if _, ok := usedParamsKeyMap[paramKey]; ok { - continue - } - removeParamKeyUnderline := utils.RemoveSymbols(paramKey) - if strings.EqualFold(fieldName, removeParamKeyUnderline) { - return paramKey, paramValue - } - } - return "", nil -} - -// bindVarToStructField sets value to struct object attribute by name. -func bindVarToStructField( - fieldValue reflect.Value, - srcValue interface{}, - cachedFieldInfo *structcache.CachedFieldInfo, - paramKeyToAttrMap map[string]string, -) (err error) { - if !fieldValue.IsValid() { - return nil - } - // CanSet checks whether attribute is public accessible. - if !fieldValue.CanSet() { - return nil - } - defer func() { - if exception := recover(); exception != nil { - if err = bindVarToReflectValue(fieldValue, srcValue, paramKeyToAttrMap); err != nil { - err = gerror.Wrapf(err, `error binding srcValue to attribute "%s"`, cachedFieldInfo.FieldName()) - } - } - }() - // Directly converting. - if empty.IsNil(srcValue) { - fieldValue.Set(reflect.Zero(fieldValue.Type())) - return nil - } - // Try to call custom converter. - // Issue: https://github.com/gogf/gf/issues/3099 - var ( - customConverterInput reflect.Value - ok bool - ) - if cachedFieldInfo.HasCustomConvert { - if customConverterInput, ok = srcValue.(reflect.Value); !ok { - customConverterInput = reflect.ValueOf(srcValue) - } - if ok, err = callCustomConverter(customConverterInput, fieldValue); ok || err != nil { - return - } - } - if cachedFieldInfo.IsCommonInterface { - if ok, err = bindVarToReflectValueWithInterfaceCheck(fieldValue, srcValue); ok || err != nil { - return - } - } - // Common types use fast assignment logic - if cachedFieldInfo.ConvertFunc != nil { - cachedFieldInfo.ConvertFunc(srcValue, fieldValue) - return nil - } - doConvertWithReflectValueSet(fieldValue, doConvertInput{ - FromValue: srcValue, - ToTypeName: cachedFieldInfo.StructField.Type.String(), - ReferValue: fieldValue, - }) - return nil -} - -// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks. -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 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 false, nil - } - pointer = reflectValue.Interface() - } - // UnmarshalValue. - if v, ok := pointer.(localinterface.IUnmarshalValue); ok { - return ok, v.UnmarshalValue(value) - } - // UnmarshalText. - if v, ok := pointer.(localinterface.IUnmarshalText); ok { - var valueBytes []byte - if b, ok := value.([]byte); ok { - valueBytes = b - } else if s, ok := value.(string); ok { - valueBytes = []byte(s) - } else if f, ok := value.(localinterface.IString); ok { - valueBytes = []byte(f.String()) - } - if len(valueBytes) > 0 { - return ok, v.UnmarshalText(valueBytes) - } - } - // UnmarshalJSON. - if v, ok := pointer.(localinterface.IUnmarshalJSON); ok { - var valueBytes []byte - if b, ok := value.([]byte); ok { - valueBytes = b - } else if s, ok := value.(string); ok { - valueBytes = []byte(s) - } else if f, ok := value.(localinterface.IString); ok { - valueBytes = []byte(f.String()) - } - - if len(valueBytes) > 0 { - // If it is not a valid JSON string, it then adds char `"` on its both sides to make it is. - if !json.Valid(valueBytes) { - newValueBytes := make([]byte, len(valueBytes)+2) - newValueBytes[0] = '"' - newValueBytes[len(newValueBytes)-1] = '"' - copy(newValueBytes[1:], valueBytes) - valueBytes = newValueBytes - } - return ok, v.UnmarshalJSON(valueBytes) - } - } - if v, ok := pointer.(localinterface.ISet); ok { - v.Set(value) - return ok, nil - } - return false, nil -} - -// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. -func bindVarToReflectValue( - structFieldValue reflect.Value, value interface{}, paramKeyToAttrMap map[string]string, -) (err error) { - // JSON content converting. - ok, err := doConvertWithJsonCheck(value, structFieldValue) - if err != nil { - return err - } - if ok { - return nil - } - - kind := structFieldValue.Kind() - // Converting using `Set` interface implements, for some types. - switch kind { - case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface: - if !structFieldValue.IsNil() { - if v, ok := structFieldValue.Interface().(localinterface.ISet); ok { - v.Set(value) - return nil - } - } - } - - // Converting using reflection by kind. - switch kind { - case reflect.Map: - return doMapToMap(value, structFieldValue, paramKeyToAttrMap) - - case reflect.Struct: - // Recursively converting for struct attribute. - if err = doStruct(value, structFieldValue, nil, ""); err != nil { - // Note there's reflect conversion mechanism here. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) - } - - // Note that the slice element might be type of struct, - // so it uses Struct function doing the converting internally. - case reflect.Slice, reflect.Array: - var ( - reflectArray reflect.Value - reflectValue = reflect.ValueOf(value) - ) - if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array { - reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len()) - if reflectValue.Len() > 0 { - var ( - elemType = reflectArray.Index(0).Type() - elemTypeName string - converted bool - ) - for i := 0; i < reflectValue.Len(); i++ { - converted = false - elemTypeName = elemType.Name() - if elemTypeName == "" { - elemTypeName = elemType.String() - } - var elem reflect.Value - if elemType.Kind() == reflect.Ptr { - elem = reflect.New(elemType.Elem()).Elem() - } else { - elem = reflect.New(elemType).Elem() - } - if elem.Kind() == reflect.Struct { - if err = doStruct(reflectValue.Index(i).Interface(), elem, nil, ""); err == nil { - converted = true - } - } - if !converted { - doConvertWithReflectValueSet(elem, doConvertInput{ - FromValue: reflectValue.Index(i).Interface(), - ToTypeName: elemTypeName, - ReferValue: elem, - }) - } - if elemType.Kind() == reflect.Ptr { - // Before it sets the `elem` to array, do pointer converting if necessary. - elem = elem.Addr() - } - reflectArray.Index(i).Set(elem) - } - } - } else { - var ( - elem reflect.Value - elemType = structFieldValue.Type().Elem() - elemTypeName = elemType.Name() - converted bool - ) - switch reflectValue.Kind() { - case reflect.String: - // Value is empty string. - if reflectValue.IsZero() { - var elemKind = elemType.Kind() - // Try to find the original type kind of the slice element. - if elemKind == reflect.Ptr { - elemKind = elemType.Elem().Kind() - } - switch elemKind { - case reflect.String: - // Empty string cannot be assigned to string slice. - return nil - } - } - } - if elemTypeName == "" { - elemTypeName = elemType.String() - } - if elemType.Kind() == reflect.Ptr { - elem = reflect.New(elemType.Elem()).Elem() - } else { - elem = reflect.New(elemType).Elem() - } - if elem.Kind() == reflect.Struct { - if err = doStruct(value, elem, nil, ""); err == nil { - converted = true - } - } - if !converted { - doConvertWithReflectValueSet(elem, doConvertInput{ - FromValue: value, - ToTypeName: elemTypeName, - ReferValue: elem, - }) - } - if elemType.Kind() == reflect.Ptr { - // Before it sets the `elem` to array, do pointer converting if necessary. - elem = elem.Addr() - } - reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1) - reflectArray.Index(0).Set(elem) - } - structFieldValue.Set(reflectArray) - - case reflect.Ptr: - if structFieldValue.IsNil() || structFieldValue.IsZero() { - // Nil or empty pointer, it creates a new one. - item := reflect.New(structFieldValue.Type().Elem()) - if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok { - structFieldValue.Set(item) - return err - } - elem := item.Elem() - if err = bindVarToReflectValue(elem, value, paramKeyToAttrMap); err == nil { - structFieldValue.Set(elem.Addr()) - } - } else { - // Not empty pointer, it assigns values to it. - return bindVarToReflectValue(structFieldValue.Elem(), value, paramKeyToAttrMap) - } - - // It mainly and specially handles the interface of nil value. - case reflect.Interface: - if value == nil { - // Specially. - structFieldValue.Set(reflect.ValueOf((*interface{})(nil))) - } else { - // Note there's reflect conversion mechanism here. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) - } - - default: - defer func() { - if exception := recover(); exception != nil { - err = gerror.NewCodef( - gcode.CodeInternalPanic, - `cannot convert value "%+v" to type "%s":%+v`, - value, - structFieldValue.Type().String(), - exception, - ) - } - }() - // It here uses reflect converting `value` to type of the attribute and assigns - // the result value to the attribute. It might fail and panic if the usual Go - // conversion rules do not allow conversion. - structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) +func StructTag(params any, pointer any, priorityTag string) (err error) { + option := StructOption{ + PriorityTag: priorityTag, + ContinueOnError: true, } - return nil + return defaultConverter.Struct(params, pointer, option) } diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 867fcdd2bbd..3bfd7b42a2b 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -6,128 +6,25 @@ package gconv -import ( - "reflect" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" -) - // Structs converts any slice to given struct slice. // Also see Scan, Struct. -func Structs(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) { +func Structs(params any, pointer any, paramKeyToAttrMap ...map[string]string) (err error) { return Scan(params, pointer, paramKeyToAttrMap...) } // SliceStruct is alias of Structs. -func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) { +func SliceStruct(params any, pointer any, mapping ...map[string]string) (err error) { return Structs(params, pointer, mapping...) } // StructsTag acts as Structs but also with support for priority tag feature, which retrieves the // specified priorityTagAndFieldName for `params` key-value items to struct attribute names mapping. // The parameter `priorityTag` supports multiple priorityTagAndFieldName that can be joined with char ','. -func StructsTag(params interface{}, pointer interface{}, priorityTag string) (err error) { - return doStructs(params, pointer, nil, priorityTag) -} - -// doStructs converts any slice to given struct slice. -// -// It automatically checks and converts json string to []map if `params` is string/[]byte. -// -// The parameter `pointer` should be type of pointer to slice of struct. -// Note that if `pointer` is a pointer to another pointer of type of slice of struct, -// it will create the struct/pointer internally. -func doStructs( - params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string, -) (err error) { - defer func() { - // Catch the panic, especially the reflection operation panics. - if exception := recover(); exception != nil { - if v, ok := exception.(error); ok && gerror.HasStack(v) { - err = v - } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) - } - } - }() - - // 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, - ) - } - } - // Converting `params` to map slice. - var ( - paramsList []interface{} - paramsRv = reflect.ValueOf(params) - paramsKind = paramsRv.Kind() - ) - for paramsKind == reflect.Ptr { - paramsRv = paramsRv.Elem() - paramsKind = paramsRv.Kind() - } - switch paramsKind { - case reflect.Slice, reflect.Array: - paramsList = make([]interface{}, paramsRv.Len()) - for i := 0; i < paramsRv.Len(); i++ { - paramsList[i] = paramsRv.Index(i).Interface() - } - default: - var paramsMaps = Maps(params) - paramsList = make([]interface{}, len(paramsMaps)) - for i := 0; i < len(paramsMaps); i++ { - paramsList[i] = paramsMaps[i] - } - } - // If `params` is an empty slice, no conversion. - if len(paramsList) == 0 { - return nil - } - var ( - reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) - itemType = reflectElemArray.Index(0).Type() - itemTypeKind = itemType.Kind() - pointerRvElem = pointerRv.Elem() - pointerRvLength = pointerRvElem.Len() - ) - if itemTypeKind == reflect.Ptr { - // Pointer element. - for i := 0; i < len(paramsList); i++ { - var tempReflectValue reflect.Value - if i < pointerRvLength { - // Might be nil. - tempReflectValue = pointerRvElem.Index(i).Elem() - } - if !tempReflectValue.IsValid() { - tempReflectValue = reflect.New(itemType.Elem()).Elem() - } - if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { - return err - } - reflectElemArray.Index(i).Set(tempReflectValue.Addr()) - } - } else { - // Struct element. - for i := 0; i < len(paramsList); i++ { - var tempReflectValue reflect.Value - if i < pointerRvLength { - tempReflectValue = pointerRvElem.Index(i) - } else { - tempReflectValue = reflect.New(itemType).Elem() - } - if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil { - return err - } - reflectElemArray.Index(i).Set(tempReflectValue) - } - } - pointerRv.Elem().Set(reflectElemArray) - return nil +func StructsTag(params any, pointer any, priorityTag string) (err error) { + return defaultConverter.Structs(params, pointer, SliceOption{ + ContinueOnError: true, + }, StructOption{ + PriorityTag: priorityTag, + ContinueOnError: true, + }) } diff --git a/util/gconv/gconv_time.go b/util/gconv/gconv_time.go index d42a0639518..1102e0e875e 100644 --- a/util/gconv/gconv_time.go +++ b/util/gconv/gconv_time.go @@ -9,39 +9,21 @@ package gconv import ( "time" - "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/os/gtime" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) // Time converts `any` to time.Time. -func Time(any interface{}, format ...string) time.Time { - // It's already this type. - if len(format) == 0 { - if v, ok := any.(time.Time); ok { - return v - } - } - if t := GTime(any, format...); t != nil { - return t.Time - } - return time.Time{} +func Time(any any, format ...string) time.Time { + t, _ := defaultConverter.Time(any, format...) + return t } // Duration converts `any` to time.Duration. // If `any` is string, then it uses time.ParseDuration to convert it. // If `any` is numeric, then it converts `any` as nanoseconds. -func Duration(any interface{}) time.Duration { - // It's already this type. - if v, ok := any.(time.Duration); ok { - return v - } - s := String(any) - if !utils.IsNumeric(s) { - d, _ := gtime.ParseDuration(s) - return d - } - return time.Duration(Int64(any)) +func Duration(any any) time.Duration { + d, _ := defaultConverter.Duration(any) + return d } // GTime converts `any` to *gtime.Time. @@ -49,43 +31,7 @@ func Duration(any interface{}) time.Duration { // It returns the converted value that matched the first format of the formats slice. // If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, // or using gtime.StrToTime if `any` is string. -func GTime(any interface{}, format ...string) *gtime.Time { - if any == nil { - return nil - } - if v, ok := any.(localinterface.IGTime); ok { - return v.GTime(format...) - } - // It's already this type. - if len(format) == 0 { - if v, ok := any.(*gtime.Time); ok { - return v - } - if t, ok := any.(time.Time); ok { - return gtime.New(t) - } - if t, ok := any.(*time.Time); ok { - return gtime.New(t) - } - } - s := String(any) - if len(s) == 0 { - return gtime.New() - } - // Priority conversion using given format. - if len(format) > 0 { - for _, item := range format { - t, err := gtime.StrToTimeFormat(s, item) - if t != nil && err == nil { - return t - } - } - return nil - } - if utils.IsNumeric(s) { - return gtime.NewFromTimeStamp(Int64(s)) - } else { - t, _ := gtime.StrToTime(s) - return t - } +func GTime(any any, format ...string) *gtime.Time { + t, _ := defaultConverter.GTime(any, format...) + return t } diff --git a/util/gconv/gconv_uint.go b/util/gconv/gconv_uint.go index 367a71a79a2..b7c9f66fd44 100644 --- a/util/gconv/gconv_uint.go +++ b/util/gconv/gconv_uint.go @@ -6,180 +6,32 @@ package gconv -import ( - "math" - "reflect" - "strconv" - - "github.com/gogf/gf/v2/encoding/gbinary" - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/util/gconv/internal/localinterface" -) - // Uint converts `any` to uint. func Uint(any any) uint { - v, _ := doUint(any) + v, _ := defaultConverter.Uint(any) return v } -func doUint(any any) (uint, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(uint); ok { - return v, nil - } - v, err := doUint64(any) - return uint(v), err -} - // Uint8 converts `any` to uint8. func Uint8(any any) uint8 { - v, _ := doUint8(any) + v, _ := defaultConverter.Uint8(any) return v } -func doUint8(any any) (uint8, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(uint8); ok { - return v, nil - } - v, err := doUint64(any) - return uint8(v), err -} - // Uint16 converts `any` to uint16. func Uint16(any any) uint16 { - v, _ := doUint16(any) + v, _ := defaultConverter.Uint16(any) return v } -func doUint16(any any) (uint16, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(uint16); ok { - return v, nil - } - v, err := doUint64(any) - return uint16(v), err -} - // Uint32 converts `any` to uint32. func Uint32(any any) uint32 { - v, _ := doUint32(any) + v, _ := defaultConverter.Uint32(any) return v } -func doUint32(any any) (uint32, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(uint32); ok { - return v, nil - } - v, err := doUint64(any) - return uint32(v), err -} - // Uint64 converts `any` to uint64. func Uint64(any any) uint64 { - v, _ := doUint64(any) + v, _ := defaultConverter.Uint64(any) return v } - -func doUint64(any any) (uint64, error) { - if any == nil { - return 0, nil - } - if v, ok := any.(uint64); ok { - return v, nil - } - rv := reflect.ValueOf(any) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val := rv.Int() - if val < 0 { - return uint64(val), gerror.NewCodef( - gcode.CodeInvalidParameter, - `cannot convert negative value "%d" to uint64`, - val, - ) - } - return uint64(val), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rv.Uint(), nil - case reflect.Uintptr: - return rv.Uint(), nil - case reflect.Float32, reflect.Float64: - val := rv.Float() - if val < 0 { - return uint64(val), gerror.NewCodef( - gcode.CodeInvalidParameter, - `cannot convert negative value "%f" to uint64`, - val, - ) - } - return uint64(val), nil - case reflect.Bool: - if rv.Bool() { - return 1, nil - } - return 0, nil - case reflect.Ptr: - if rv.IsNil() { - return 0, nil - } - if f, ok := any.(localinterface.IUint64); ok { - return f.Uint64(), nil - } - return doUint64(rv.Elem().Interface()) - case reflect.Slice: - if rv.Type().Elem().Kind() == reflect.Uint8 { - return gbinary.DecodeToUint64(rv.Bytes()), nil - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport slice type "%s" for converting to uint64`, - rv.Type().String(), - ) - case reflect.String: - var s = rv.String() - // Hexadecimal - if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { - v, err := strconv.ParseUint(s[2:], 16, 64) - if err == nil { - return v, nil - } - return 0, gerror.WrapCodef( - gcode.CodeInvalidParameter, - err, - `cannot convert hexadecimal string "%s" to uint64`, - s, - ) - } - // Decimal - if v, err := strconv.ParseUint(s, 10, 64); err == nil { - return v, nil - } - // Float64 - if v, err := doFloat64(any); err == nil { - if math.IsNaN(v) { - return 0, nil - } - return uint64(v), nil - } - default: - if f, ok := any.(localinterface.IUint64); ok { - return f.Uint64(), nil - } - } - return 0, gerror.NewCodef( - gcode.CodeInvalidParameter, - `unsupport value type "%s" for converting to uint64`, - reflect.TypeOf(any).String(), - ) -} diff --git a/util/gconv/gconv_z_bench_struct_test.go b/util/gconv/gconv_z_bench_struct_test.go index 5fb2efe5928..d091a7dfb3d 100644 --- a/util/gconv/gconv_z_bench_struct_test.go +++ b/util/gconv/gconv_z_bench_struct_test.go @@ -92,7 +92,7 @@ func Benchmark_Struct_Basic(b *testing.B) { func Benchmark_doStruct_Fields8_Basic_MapToStruct(b *testing.B) { for i := 0; i < b.N; i++ { - doStruct(structMapFields8, structPointer8, map[string]string{}, "") + defaultConverter.Struct(structMapFields8, structPointer8, StructOption{}) } } diff --git a/util/gconv/gconv_z_unit_converter_test.go b/util/gconv/gconv_z_unit_converter_test.go index ad88c3175c3..ccaee32b4d3 100644 --- a/util/gconv/gconv_z_unit_converter_test.go +++ b/util/gconv/gconv_z_unit_converter_test.go @@ -7,6 +7,9 @@ package gconv_test import ( + "database/sql" + "fmt" + "reflect" "testing" "github.com/gogf/gf/v2/test/gtest" @@ -84,3 +87,87 @@ func TestConvertWithRefer(t *testing.T) { t.AssertNE(gconv.ConvertWithRefer("1.01", false), false) }) } + +func testAnyToMyInt(from any, to reflect.Value) error { + switch x := from.(type) { + case int: + to.SetInt(123456) + default: + return fmt.Errorf("unsupported type %T(%v)", x, x) + } + return nil +} + +func testAnyToSqlNullType(_ any, to reflect.Value) error { + if to.Kind() != reflect.Ptr { + to = to.Addr() + } + return to.Interface().(sql.Scanner).Scan(123456) +} + +func TestNewConverter(t *testing.T) { + type Dst[T any] struct { + A T + } + gtest.C(t, func(t *gtest.T) { + conv := gconv.NewConverter() + conv.RegisterAnyConverterFunc(testAnyToMyInt, reflect.TypeOf((*myInt)(nil))) + var dst Dst[myInt] + err := conv.Struct(map[string]any{ + "a": 1200, + }, &dst, gconv.StructOption{}) + t.AssertNil(err) + t.Assert(dst, Dst[myInt]{ + A: 123456, + }) + }) + gtest.C(t, func(t *gtest.T) { + conv := gconv.NewConverter() + conv.RegisterAnyConverterFunc(testAnyToMyInt, reflect.TypeOf((myInt)(0))) + var dst Dst[*myInt] + err := conv.Struct(map[string]any{ + "a": 1200, + }, &dst, gconv.StructOption{}) + t.AssertNil(err) + t.Assert(*dst.A, 123456) + }) + + gtest.C(t, func(t *gtest.T) { + conv := gconv.NewConverter() + conv.RegisterAnyConverterFunc(testAnyToSqlNullType, reflect.TypeOf((*sql.Scanner)(nil))) + type sqlNullDst struct { + A sql.Null[int] + B sql.Null[float32] + C sql.NullInt64 + D sql.NullString + + E *sql.Null[int] + F *sql.Null[float32] + G *sql.NullInt64 + H *sql.NullString + } + var dst sqlNullDst + err := conv.Struct(map[string]any{ + "a": 12, + "b": 34, + "c": 56, + "d": "sqlNullString", + "e": 12, + "f": 34, + "g": 56, + "h": "sqlNullString", + }, &dst, gconv.StructOption{}) + t.AssertNil(err) + t.Assert(dst, sqlNullDst{ + A: sql.Null[int]{V: 123456, Valid: true}, + B: sql.Null[float32]{V: 123456, Valid: true}, + C: sql.NullInt64{Int64: 123456, Valid: true}, + D: sql.NullString{String: "123456", Valid: true}, + + E: &sql.Null[int]{V: 123456, Valid: true}, + F: &sql.Null[float32]{V: 123456, Valid: true}, + G: &sql.NullInt64{Int64: 123456, Valid: true}, + H: &sql.NullString{String: "123456", Valid: true}, + }) + }) +} diff --git a/util/gconv/internal/converter/converter.go b/util/gconv/internal/converter/converter.go new file mode 100644 index 00000000000..cd69edda5c7 --- /dev/null +++ b/util/gconv/internal/converter/converter.go @@ -0,0 +1,180 @@ +// 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 converter provides converting utilities for any types of variables. +package converter + +import ( + "reflect" + "time" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/structcache" +) + +// AnyConvertFunc is the type for any type converting function. +type AnyConvertFunc = structcache.AnyConvertFunc + +// RecursiveType is the type for converting recursively. +type RecursiveType string + +const ( + RecursiveTypeAuto RecursiveType = "auto" + RecursiveTypeTrue RecursiveType = "true" +) + +type ( + converterInType = reflect.Type + converterOutType = reflect.Type + converterFunc = reflect.Value +) + +// Converter implements the interface Converter. +type Converter struct { + internalConverter *structcache.Converter + typeConverterFuncMap map[converterInType]map[converterOutType]converterFunc +} + +var ( + // Empty strings. + emptyStringMap = map[string]struct{}{ + "": {}, + "0": {}, + "no": {}, + "off": {}, + "false": {}, + } +) + +// NewConverter creates and returns management object for type converting. +func NewConverter() *Converter { + cf := &Converter{ + internalConverter: structcache.NewConverter(), + typeConverterFuncMap: make(map[converterInType]map[converterOutType]converterFunc), + } + cf.registerBuiltInAnyConvertFunc() + return cf +} + +// RegisterTypeConverterFunc registers custom converter. +// It must be registered before you use this custom converting feature. +// It is suggested to do it in boot procedure of the process. +// +// Note: +// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`. +// It will convert type `T1` to type `T2`. +// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer. +func (c *Converter) RegisterTypeConverterFunc(f any) (err error) { + var ( + fReflectType = reflect.TypeOf(f) + errType = reflect.TypeOf((*error)(nil)).Elem() + ) + if fReflectType.Kind() != reflect.Func || + fReflectType.NumIn() != 1 || fReflectType.NumOut() != 2 || + !fReflectType.Out(1).Implements(errType) { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + "parameter must be type of converter function and defined as pattern `func(T1) (T2, error)`, "+ + "but defined as `%s`", + fReflectType.String(), + ) + return + } + + // The Key and Value of the converter map should not be pointer. + var ( + inType = fReflectType.In(0) + outType = fReflectType.Out(0) + ) + if inType.Kind() == reflect.Pointer { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + "invalid converter function `%s`: invalid input parameter type `%s`, should not be type of pointer", + fReflectType.String(), inType.String(), + ) + return + } + if outType.Kind() != reflect.Pointer { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + "invalid converter function `%s`: invalid output parameter type `%s` should be type of pointer", + fReflectType.String(), outType.String(), + ) + return + } + + registeredOutTypeMap, ok := c.typeConverterFuncMap[inType] + if !ok { + registeredOutTypeMap = make(map[converterOutType]converterFunc) + c.typeConverterFuncMap[inType] = registeredOutTypeMap + } + if _, ok = registeredOutTypeMap[outType]; ok { + err = gerror.NewCodef( + gcode.CodeInvalidOperation, + "the converter parameter type `%s` to type `%s` has already been registered", + inType.String(), outType.String(), + ) + return + } + registeredOutTypeMap[outType] = reflect.ValueOf(f) + c.internalConverter.MarkTypeConvertFunc(outType) + return +} + +// RegisterAnyConverterFunc registers custom type converting function for specified types. +func (c *Converter) RegisterAnyConverterFunc(convertFunc AnyConvertFunc, types ...reflect.Type) { + for _, t := range types { + c.internalConverter.RegisterAnyConvertFunc(t, convertFunc) + } +} + +func (c *Converter) registerBuiltInAnyConvertFunc() { + var ( + intType = reflect.TypeOf(0) + int8Type = reflect.TypeOf(int8(0)) + int16Type = reflect.TypeOf(int16(0)) + int32Type = reflect.TypeOf(int32(0)) + int64Type = reflect.TypeOf(int64(0)) + uintType = reflect.TypeOf(uint(0)) + uint8Type = reflect.TypeOf(uint8(0)) + uint16Type = reflect.TypeOf(uint16(0)) + uint32Type = reflect.TypeOf(uint32(0)) + uint64Type = reflect.TypeOf(uint64(0)) + float32Type = reflect.TypeOf(float32(0)) + float64Type = reflect.TypeOf(float64(0)) + stringType = reflect.TypeOf("") + bytesType = reflect.TypeOf([]byte{}) + boolType = reflect.TypeOf(false) + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() + gtimeType = reflect.TypeOf((*gtime.Time)(nil)).Elem() + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForInt64, intType, int8Type, int16Type, int32Type, int64Type, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForUint64, uintType, uint8Type, uint16Type, uint32Type, uint64Type, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForString, stringType, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForFloat64, float32Type, float64Type, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForBool, boolType, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForBytes, bytesType, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForTime, timeType, + ) + c.RegisterAnyConverterFunc( + c.builtInAnyConvertFuncForGTime, gtimeType, + ) +} diff --git a/util/gconv/internal/converter/converter_bool.go b/util/gconv/internal/converter/converter_bool.go new file mode 100644 index 00000000000..f8d9ce7c953 --- /dev/null +++ b/util/gconv/internal/converter/converter_bool.go @@ -0,0 +1,75 @@ +// 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 converter + +import ( + "reflect" + "strings" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Bool converts `any` to bool. +func (c *Converter) Bool(any any) (bool, error) { + if empty.IsNil(any) { + return false, nil + } + switch value := any.(type) { + case bool: + return value, nil + case []byte: + if _, ok := emptyStringMap[strings.ToLower(string(value))]; ok { + return false, nil + } + return true, nil + case string: + if _, ok := emptyStringMap[strings.ToLower(value)]; ok { + return false, nil + } + return true, nil + default: + if f, ok := value.(localinterface.IBool); ok { + return f.Bool(), nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return false, nil + } + if rv.Type().Elem().Kind() == reflect.Bool { + return rv.Elem().Bool(), nil + } + return c.Bool(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() != 0, nil + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() != 0, nil + case reflect.Float32, reflect.Float64: + return rv.Float() != 0, nil + case reflect.Bool: + return rv.Bool(), nil + // TODO:(Map,Array,Slice,Struct) It might panic here for these types. + case reflect.Map, reflect.Array: + fallthrough + case reflect.Slice: + return rv.Len() != 0, nil + case reflect.Struct: + return true, nil + default: + s, err := c.String(any) + if err != nil { + return false, err + } + if _, ok := emptyStringMap[strings.ToLower(s)]; ok { + return false, nil + } + return true, nil + } + } +} diff --git a/util/gconv/internal/converter/converter_builtin.go b/util/gconv/internal/converter/converter_builtin.go new file mode 100644 index 00000000000..f7ac1cf0e37 --- /dev/null +++ b/util/gconv/internal/converter/converter_builtin.go @@ -0,0 +1,89 @@ +// 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 converter + +import ( + "reflect" + "time" + + "github.com/gogf/gf/v2/os/gtime" +) + +func (c *Converter) builtInAnyConvertFuncForInt64(from any, to reflect.Value) error { + v, err := c.Int64(from) + if err != nil { + return err + } + to.SetInt(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForUint64(from any, to reflect.Value) error { + v, err := c.Uint64(from) + if err != nil { + return err + } + to.SetUint(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForString(from any, to reflect.Value) error { + v, err := c.String(from) + if err != nil { + return err + } + to.SetString(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForFloat64(from any, to reflect.Value) error { + v, err := c.Float64(from) + if err != nil { + return err + } + to.SetFloat(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForBool(from any, to reflect.Value) error { + v, err := c.Bool(from) + if err != nil { + return err + } + to.SetBool(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForBytes(from any, to reflect.Value) error { + v, err := c.Bytes(from) + if err != nil { + return err + } + to.SetBytes(v) + return nil +} + +func (c *Converter) builtInAnyConvertFuncForTime(from any, to reflect.Value) error { + t, err := c.Time(from) + if err != nil { + return err + } + *to.Addr().Interface().(*time.Time) = t + return nil +} + +func (c *Converter) builtInAnyConvertFuncForGTime(from any, to reflect.Value) error { + v, err := c.GTime(from) + if err != nil { + return err + } + if v == nil { + v = gtime.New() + } + *to.Addr().Interface().(*gtime.Time) = *v + return nil +} diff --git a/util/gconv/internal/converter/converter_bytes.go b/util/gconv/internal/converter/converter_bytes.go new file mode 100644 index 00000000000..79cd23378ec --- /dev/null +++ b/util/gconv/internal/converter/converter_bytes.go @@ -0,0 +1,68 @@ +// 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 converter + +import ( + "math" + "reflect" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Bytes converts `any` to []byte. +func (c *Converter) Bytes(any any) ([]byte, error) { + if empty.IsNil(any) { + return nil, nil + } + switch value := any.(type) { + case string: + return []byte(value), nil + + case []byte: + return value, nil + + default: + if f, ok := value.(localinterface.IBytes); ok { + return f.Bytes(), nil + } + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Map: + bytes, err := json.Marshal(any) + if err != nil { + return nil, err + } + return bytes, nil + + case reflect.Array, reflect.Slice: + var ( + ok = true + bytes = make([]byte, originValueAndKind.OriginValue.Len()) + ) + for i := range bytes { + int32Value, err := c.Int32(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil { + return nil, err + } + if int32Value < 0 || int32Value > math.MaxUint8 { + ok = false + break + } + bytes[i] = byte(int32Value) + } + if ok { + return bytes, nil + } + default: + } + return gbinary.Encode(any), nil + } +} diff --git a/util/gconv/internal/converter/converter_convert.go b/util/gconv/internal/converter/converter_convert.go new file mode 100644 index 00000000000..ec1f545cfdd --- /dev/null +++ b/util/gconv/internal/converter/converter_convert.go @@ -0,0 +1,563 @@ +// 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 converter + +import ( + "reflect" + "time" + + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gtime" +) + +// ConvertOption is the option for converting. +type ConvertOption struct { + // ExtraParams are extra values for implementing the converting. + ExtraParams []any + SliceOption SliceOption + MapOption MapOption + StructOption StructOption +} + +// ConvertWithTypeName converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string. +func (c *Converter) ConvertWithTypeName(fromValue any, toTypeName string, option ConvertOption) (any, error) { + return c.doConvert( + doConvertInput{ + FromValue: fromValue, + ToTypeName: toTypeName, + ReferValue: nil, + }, + option, + ) +} + +// ConvertWithRefer converts the variable `fromValue` to the type referred by value `referValue`. +func (c *Converter) ConvertWithRefer(fromValue, referValue any, option ConvertOption) (any, error) { + var referValueRf reflect.Value + if v, ok := referValue.(reflect.Value); ok { + referValueRf = v + } else { + referValueRf = reflect.ValueOf(referValue) + } + return c.doConvert( + doConvertInput{ + FromValue: fromValue, + ToTypeName: referValueRf.Type().String(), + ReferValue: referValue, + }, + option, + ) +} + +type doConvertInput struct { + FromValue any // Value that is converted from. + ToTypeName string // Target value type name in string. + ReferValue any // Referred value, a value in type `ToTypeName`. Note that its type might be reflect.Value. + + // Marks that the value is already converted and set to `ReferValue`. Caller can ignore the returned result. + // It is an attribute for internal usage purpose. + alreadySetToReferValue bool +} + +// doConvert does commonly use types converting. +func (c *Converter) doConvert(in doConvertInput, option ConvertOption) (convertedValue any, err error) { + switch in.ToTypeName { + case "int": + return c.Int(in.FromValue) + case "*int": + if _, ok := in.FromValue.(*int); ok { + return in.FromValue, nil + } + v, err := c.Int(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "int8": + return c.Int8(in.FromValue) + case "*int8": + if _, ok := in.FromValue.(*int8); ok { + return in.FromValue, nil + } + v, err := c.Int8(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "int16": + return c.Int16(in.FromValue) + case "*int16": + if _, ok := in.FromValue.(*int16); ok { + return in.FromValue, nil + } + v, err := c.Int16(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "int32": + return c.Int32(in.FromValue) + case "*int32": + if _, ok := in.FromValue.(*int32); ok { + return in.FromValue, nil + } + v, err := c.Int32(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "int64": + return c.Int64(in.FromValue) + case "*int64": + if _, ok := in.FromValue.(*int64); ok { + return in.FromValue, nil + } + v, err := c.Int64(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "uint": + return c.Uint(in.FromValue) + case "*uint": + if _, ok := in.FromValue.(*uint); ok { + return in.FromValue, nil + } + v, err := c.Uint(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "uint8": + return c.Uint8(in.FromValue) + case "*uint8": + if _, ok := in.FromValue.(*uint8); ok { + return in.FromValue, nil + } + v, err := c.Uint8(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "uint16": + return c.Uint16(in.FromValue) + case "*uint16": + if _, ok := in.FromValue.(*uint16); ok { + return in.FromValue, nil + } + v, err := c.Uint16(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "uint32": + return c.Uint32(in.FromValue) + case "*uint32": + if _, ok := in.FromValue.(*uint32); ok { + return in.FromValue, nil + } + v, err := c.Uint32(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "uint64": + return c.Uint64(in.FromValue) + case "*uint64": + if _, ok := in.FromValue.(*uint64); ok { + return in.FromValue, nil + } + v, err := c.Uint64(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "float32": + return c.Float32(in.FromValue) + case "*float32": + if _, ok := in.FromValue.(*float32); ok { + return in.FromValue, nil + } + v, err := c.Float32(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "float64": + return c.Float64(in.FromValue) + case "*float64": + if _, ok := in.FromValue.(*float64); ok { + return in.FromValue, nil + } + v, err := c.Float64(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "bool": + return c.Bool(in.FromValue) + case "*bool": + if _, ok := in.FromValue.(*bool); ok { + return in.FromValue, nil + } + v, err := c.Bool(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "string": + return c.String(in.FromValue) + case "*string": + if _, ok := in.FromValue.(*string); ok { + return in.FromValue, nil + } + v, err := c.String(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "[]byte": + return c.Bytes(in.FromValue) + case "[]int": + return c.SliceInt(in.FromValue, option.SliceOption) + case "[]int32": + return c.SliceInt32(in.FromValue, option.SliceOption) + case "[]int64": + return c.SliceInt64(in.FromValue, option.SliceOption) + case "[]uint": + return c.SliceUint(in.FromValue, option.SliceOption) + case "[]uint8": + return c.Bytes(in.FromValue) + case "[]uint32": + return c.SliceUint32(in.FromValue, option.SliceOption) + case "[]uint64": + return c.SliceUint64(in.FromValue, option.SliceOption) + case "[]float32": + return c.SliceFloat32(in.FromValue, option.SliceOption) + case "[]float64": + return c.SliceFloat64(in.FromValue, option.SliceOption) + case "[]string": + return c.SliceStr(in.FromValue, option.SliceOption) + + case "Time", "time.Time": + if len(option.ExtraParams) > 0 { + s, err := c.String(option.ExtraParams[0]) + if err != nil { + return nil, err + } + return c.Time(in.FromValue, s) + } + return c.Time(in.FromValue) + case "*time.Time": + var v time.Time + if len(option.ExtraParams) > 0 { + s, err := c.String(option.ExtraParams[0]) + if err != nil { + return time.Time{}, err + } + v, err = c.Time(in.FromValue, s) + if err != nil { + return time.Time{}, err + } + } else { + if _, ok := in.FromValue.(*time.Time); ok { + return in.FromValue, nil + } + v, err = c.Time(in.FromValue) + if err != nil { + return time.Time{}, err + } + } + return &v, nil + + case "GTime", "gtime.Time": + if len(option.ExtraParams) > 0 { + s, err := c.String(option.ExtraParams[0]) + if err != nil { + return *gtime.New(), err + } + v, err := c.GTime(in.FromValue, s) + if err != nil { + return *gtime.New(), err + } + if v != nil { + return *v, nil + } + return *gtime.New(), nil + } + v, err := c.GTime(in.FromValue) + if err != nil { + return *gtime.New(), err + } + if v != nil { + return *v, nil + } + return *gtime.New(), nil + case "*gtime.Time": + if len(option.ExtraParams) > 0 { + s, err := c.String(option.ExtraParams[0]) + if err != nil { + return gtime.New(), err + } + v, err := c.GTime(in.FromValue, s) + if err != nil { + return gtime.New(), err + } + if v != nil { + return v, nil + } + return gtime.New(), nil + } + v, err := c.GTime(in.FromValue) + if err != nil { + return gtime.New(), err + } + if v != nil { + return v, nil + } + return gtime.New(), nil + + case "Duration", "time.Duration": + return c.Duration(in.FromValue) + case "*time.Duration": + if _, ok := in.FromValue.(*time.Duration); ok { + return in.FromValue, nil + } + v, err := c.Duration(in.FromValue) + if err != nil { + return nil, err + } + return &v, nil + + case "map[string]string": + return c.MapStrStr(in.FromValue, option.MapOption) + + case "map[string]interface {}": + return c.Map(in.FromValue, option.MapOption) + + case "[]map[string]interface {}": + return c.SliceMap(in.FromValue, option.SliceOption, option.MapOption) + + case "RawMessage", "json.RawMessage": + // issue 3449 + bytes, err := json.Marshal(in.FromValue) + if err != nil { + return nil, err + } + return bytes, nil + + default: + return c.doConvertForDefault(in, option) + } +} + +func (c *Converter) doConvertForDefault(in doConvertInput, option ConvertOption) (convertedValue any, err error) { + if in.ReferValue != nil { + var referReflectValue reflect.Value + if v, ok := in.ReferValue.(reflect.Value); ok { + referReflectValue = v + } else { + referReflectValue = reflect.ValueOf(in.ReferValue) + } + var fromReflectValue reflect.Value + if v, ok := in.FromValue.(reflect.Value); ok { + fromReflectValue = v + } else { + fromReflectValue = reflect.ValueOf(in.FromValue) + } + + // custom converter. + dstReflectValue, ok, err := c.callCustomConverterWithRefer(fromReflectValue, referReflectValue) + if err != nil { + return nil, err + } + if ok { + return dstReflectValue.Interface(), nil + } + + defer func() { + if recover() != nil { + in.alreadySetToReferValue = false + if err = c.bindVarToReflectValue(referReflectValue, in.FromValue, option.StructOption); err == nil { + in.alreadySetToReferValue = true + convertedValue = referReflectValue.Interface() + } + } + }() + switch referReflectValue.Kind() { + case reflect.Ptr: + // Type converting for custom type pointers. + // Eg: + // type PayMode int + // type Req struct{ + // Mode *PayMode + // } + // + // Struct(`{"Mode": 1000}`, &req) + originType := referReflectValue.Type().Elem() + switch originType.Kind() { + case reflect.Struct: + // Not support some kinds. + default: + in.ToTypeName = originType.Kind().String() + in.ReferValue = nil + result, err := c.doConvert(in, option) + if err != nil { + return nil, err + } + refElementValue := reflect.ValueOf(result) + originTypeValue := reflect.New(refElementValue.Type()).Elem() + originTypeValue.Set(refElementValue) + in.alreadySetToReferValue = true + return originTypeValue.Addr().Convert(referReflectValue.Type()).Interface(), nil + } + + case reflect.Map: + var targetValue = reflect.New(referReflectValue.Type()).Elem() + if err = c.MapToMap(in.FromValue, targetValue, nil, option.MapOption); err == nil { + in.alreadySetToReferValue = true + } + return targetValue.Interface(), nil + + default: + } + in.ToTypeName = referReflectValue.Kind().String() + in.ReferValue = nil + in.alreadySetToReferValue = true + result, err := c.doConvert(in, option) + if err != nil { + return nil, err + } + convertedValue = reflect.ValueOf(result).Convert(referReflectValue.Type()).Interface() + return convertedValue, nil + } + return in.FromValue, nil +} + +func (c *Converter) doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput, option ConvertOption) error { + convertedValue, err := c.doConvert(in, option) + if err != nil { + return err + } + if !in.alreadySetToReferValue { + reflectValue.Set(reflect.ValueOf(convertedValue)) + } + return err +} + +func (c *Converter) getRegisteredConverterFuncAndSrcType( + srcReflectValue, dstReflectValueForRefer reflect.Value, +) (f converterFunc, srcType reflect.Type, ok bool) { + if len(c.typeConverterFuncMap) == 0 { + return reflect.Value{}, nil, false + } + srcType = srcReflectValue.Type() + for srcType.Kind() == reflect.Pointer { + srcType = srcType.Elem() + } + var registeredOutTypeMap map[converterOutType]converterFunc + // firstly, it searches the map by input parameter type. + registeredOutTypeMap, ok = c.typeConverterFuncMap[srcType] + if !ok { + return reflect.Value{}, nil, false + } + var dstType = dstReflectValueForRefer.Type() + if dstType.Kind() == reflect.Pointer { + // Might be **struct, which is support as designed. + if dstType.Elem().Kind() == reflect.Pointer { + dstType = dstType.Elem() + } + } else if dstReflectValueForRefer.IsValid() && dstReflectValueForRefer.CanAddr() { + dstType = dstReflectValueForRefer.Addr().Type() + } else { + dstType = reflect.PointerTo(dstType) + } + // secondly, it searches the input parameter type map + // and finds the result converter function by the output parameter type. + f, ok = registeredOutTypeMap[dstType] + if !ok { + return reflect.Value{}, nil, false + } + return +} + +func (c *Converter) callCustomConverterWithRefer( + srcReflectValue, referReflectValue reflect.Value, +) (dstReflectValue reflect.Value, converted bool, err error) { + registeredConverterFunc, srcType, ok := c.getRegisteredConverterFuncAndSrcType(srcReflectValue, referReflectValue) + if !ok { + return reflect.Value{}, false, nil + } + dstReflectValue = reflect.New(referReflectValue.Type()).Elem() + converted, err = c.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) + return +} + +// callCustomConverter call the custom converter. It will try some possible type. +func (c *Converter) callCustomConverter(srcReflectValue, dstReflectValue reflect.Value) (converted bool, err error) { + registeredConverterFunc, srcType, ok := c.getRegisteredConverterFuncAndSrcType(srcReflectValue, dstReflectValue) + if !ok { + return false, nil + } + return c.doCallCustomConverter(srcReflectValue, dstReflectValue, registeredConverterFunc, srcType) +} + +func (c *Converter) doCallCustomConverter( + srcReflectValue reflect.Value, + dstReflectValue reflect.Value, + registeredConverterFunc converterFunc, + srcType reflect.Type, +) (converted bool, err error) { + // Converter function calling. + for srcReflectValue.Type() != srcType { + srcReflectValue = srcReflectValue.Elem() + } + result := registeredConverterFunc.Call([]reflect.Value{srcReflectValue}) + if !result[1].IsNil() { + return false, result[1].Interface().(error) + } + // The `result[0]` is a pointer. + if result[0].IsNil() { + return false, nil + } + var resultValue = result[0] + for { + if resultValue.Type() == dstReflectValue.Type() && dstReflectValue.CanSet() { + dstReflectValue.Set(resultValue) + converted = true + } else if dstReflectValue.Kind() == reflect.Pointer { + if resultValue.Type() == dstReflectValue.Elem().Type() && dstReflectValue.Elem().CanSet() { + dstReflectValue.Elem().Set(resultValue) + converted = true + } + } + if converted { + break + } + if resultValue.Kind() == reflect.Pointer { + resultValue = resultValue.Elem() + } else { + break + } + } + + return converted, nil +} diff --git a/util/gconv/internal/converter/converter_float.go b/util/gconv/internal/converter/converter_float.go new file mode 100644 index 00000000000..21418100db6 --- /dev/null +++ b/util/gconv/internal/converter/converter_float.go @@ -0,0 +1,147 @@ +// 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 converter + +import ( + "reflect" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Float32 converts `any` to float32. +func (c *Converter) Float32(any any) (float32, error) { + if empty.IsNil(any) { + return 0, nil + } + switch value := any.(type) { + case float32: + return value, nil + case float64: + return float32(value), nil + case []byte: + // TODO: It might panic here for these types. + return gbinary.DecodeToFloat32(value), nil + default: + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float32(rv.Int()), nil + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float32(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return float32(rv.Float()), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + f, err := strconv.ParseFloat(rv.String(), 32) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, + ) + } + return float32(f), nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32(), nil + } + return c.Float32(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32(), nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + v, err := strconv.ParseFloat(s, 32) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float32 failed for: %v", any, + ) + } + return float32(v), nil + } + } +} + +// Float64 converts `any` to float64. +func (c *Converter) Float64(any any) (float64, error) { + if empty.IsNil(any) { + return 0, nil + } + switch value := any.(type) { + case float32: + return float64(value), nil + case float64: + return value, nil + case []byte: + // TODO: It might panic here for these types. + return gbinary.DecodeToFloat64(value), nil + default: + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(rv.Uint()), nil + case reflect.Uintptr: + return float64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + // Please Note: + // When the type is float32 or a custom type defined based on float32, + // switching to float64 may result in a few extra decimal places. + return rv.Float(), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.String: + f, err := strconv.ParseFloat(rv.String(), 64) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, + ) + } + return f, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64(), nil + } + return c.Float64(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64(), nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "converting string to float64 failed for: %v", any, + ) + } + return v, nil + } + } +} diff --git a/util/gconv/internal/converter/converter_int.go b/util/gconv/internal/converter/converter_int.go new file mode 100644 index 00000000000..dc82e926cd8 --- /dev/null +++ b/util/gconv/internal/converter/converter_int.go @@ -0,0 +1,157 @@ +// 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 converter + +import ( + "math" + "reflect" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Int converts `any` to int. +func (c *Converter) Int(any any) (int, error) { + if v, ok := any.(int); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int(v), nil +} + +// Int8 converts `any` to int8. +func (c *Converter) Int8(any any) (int8, error) { + if v, ok := any.(int8); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int8(v), nil +} + +// Int16 converts `any` to int16. +func (c *Converter) Int16(any any) (int16, error) { + if v, ok := any.(int16); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int16(v), nil +} + +// Int32 converts `any` to int32. +func (c *Converter) Int32(any any) (int32, error) { + if v, ok := any.(int32); ok { + return v, nil + } + v, err := c.Int64(any) + if err != nil { + return 0, err + } + return int32(v), nil +} + +// Int64 converts `any` to int64. +func (c *Converter) Int64(any any) (int64, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(int64); ok { + return v, nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(rv.Uint()), nil + case reflect.Uintptr: + return int64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + return int64(rv.Float()), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := any.(localinterface.IInt64); ok { + return f.Int64(), nil + } + return c.Int64(rv.Elem().Interface()) + case reflect.Slice: + // TODO: It might panic here for these types. + if rv.Type().Elem().Kind() == reflect.Uint8 { + return gbinary.DecodeToInt64(rv.Bytes()), nil + } + case reflect.String: + var ( + s = rv.String() + isMinus = false + ) + if len(s) > 0 { + if s[0] == '-' { + isMinus = true + s = s[1:] + } else if s[0] == '+' { + s = s[1:] + } + } + // Hexadecimal. + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + if v, e := strconv.ParseInt(s[2:], 16, 64); e == nil { + if isMinus { + return -v, nil + } + return v, nil + } + } + // Decimal. + if v, e := strconv.ParseInt(s, 10, 64); e == nil { + if isMinus { + return -v, nil + } + return v, nil + } + // Float64. + valueInt64, err := c.Float64(s) + if err != nil { + return 0, err + } + if math.IsNaN(valueInt64) { + return 0, nil + } else { + if isMinus { + return -int64(valueInt64), nil + } + return int64(valueInt64), nil + } + default: + if f, ok := any.(localinterface.IInt64); ok { + return f.Int64(), nil + } + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport value type for converting to int64: %v`, + reflect.TypeOf(any), + ) +} diff --git a/util/gconv/internal/converter/converter_map.go b/util/gconv/internal/converter/converter_map.go new file mode 100644 index 00000000000..1b8b93e4814 --- /dev/null +++ b/util/gconv/internal/converter/converter_map.go @@ -0,0 +1,646 @@ +// 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 converter + +import ( + "reflect" + "strings" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" + "github.com/gogf/gf/v2/util/gtag" +) + +// MapOption specifies the option for map converting. +type MapOption struct { + // Deep marks doing Map function recursively, which means if the attribute of given converting value + // is also a struct/*struct, it automatically calls Map function on this attribute converting it to + // a map[string]any type variable. + Deep bool + + // OmitEmpty ignores the attributes that has json `omitempty` tag. + OmitEmpty bool + + // Tags specifies the converted map key name by struct tag name. + Tags []string + + // ContinueOnError specifies whether to continue converting the next element + // if one element converting fails. + ContinueOnError bool +} + +// Map converts any variable `value` to map[string]any. If the parameter `value` is not a +// map/struct/*struct type, then the conversion will fail and returns nil. +// +// If `value` is a struct/*struct object, the second parameter `priorityTagAndFieldName` specifies the most priority +// priorityTagAndFieldName that will be detected, otherwise it detects the priorityTagAndFieldName in order of: +// gconv, json, field name. +func (c *Converter) Map(value any, option MapOption) (map[string]any, error) { + return c.doMapConvert(value, RecursiveTypeAuto, false, option) +} + +// MapStrStr converts `value` to map[string]string. +// Note that there might be data copy for this map type converting. +func (c *Converter) MapStrStr(value any, option MapOption) (map[string]string, error) { + if r, ok := value.(map[string]string); ok { + return r, nil + } + m, err := c.Map(value, option) + if err != nil && !option.ContinueOnError { + return nil, err + } + if len(m) > 0 { + var ( + s string + vMap = make(map[string]string, len(m)) + ) + for k, v := range m { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + vMap[k] = s + } + return vMap, nil + } + return nil, nil +} + +// MapConvert implements the map converting. +// It automatically checks and converts json string to map if `value` is string/[]byte. +// +// TODO completely implement the recursive converting for all types, especially the map. +func (c *Converter) doMapConvert( + value any, recursive RecursiveType, mustMapReturn bool, option MapOption, +) (map[string]any, error) { + if value == nil { + return nil, nil + } + // It redirects to its underlying value if it has implemented interface iVal. + if v, ok := value.(localinterface.IVal); ok { + value = v.Val() + } + var ( + err error + newTags = gtag.StructTagPriority + ) + if option.Deep { + recursive = RecursiveTypeTrue + } + switch len(option.Tags) { + case 0: + // No need handling. + case 1: + newTags = append(strings.Split(option.Tags[0], ","), gtag.StructTagPriority...) + default: + newTags = append(option.Tags, gtag.StructTagPriority...) + } + // Assert the common combination of types, and finally it uses reflection. + dataMap := make(map[string]interface{}) + switch r := value.(type) { + case string: + // If it is a JSON string, automatically unmarshal it! + if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { + if err = json.UnmarshalUseNumber([]byte(r), &dataMap); err != nil { + return nil, err + } + } else { + return nil, nil + } + case []byte: + // If it is a JSON string, automatically unmarshal it! + if len(r) > 0 && r[0] == '{' && r[len(r)-1] == '}' { + if err = json.UnmarshalUseNumber(r, &dataMap); err != nil { + return nil, err + } + } else { + return nil, nil + } + case map[interface{}]interface{}: + recursiveOption := option + recursiveOption.Tags = newTags + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == RecursiveTypeTrue, + Option: recursiveOption, + }, + ) + if err != nil && !option.ContinueOnError { + return nil, err + } + } + case map[interface{}]string: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[interface{}]int: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[interface{}]uint: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[interface{}]float32: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[interface{}]float64: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[string]bool: + for k, v := range r { + dataMap[k] = v + } + case map[string]int: + for k, v := range r { + dataMap[k] = v + } + case map[string]uint: + for k, v := range r { + dataMap[k] = v + } + case map[string]float32: + for k, v := range r { + dataMap[k] = v + } + case map[string]float64: + for k, v := range r { + dataMap[k] = v + } + case map[string]string: + for k, v := range r { + dataMap[k] = v + } + case map[string]interface{}: + if recursive == RecursiveTypeTrue { + recursiveOption := option + recursiveOption.Tags = newTags + // A copy of current map. + for k, v := range r { + dataMap[k], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == RecursiveTypeTrue, + Option: recursiveOption, + }, + ) + if err != nil && !option.ContinueOnError { + return nil, err + } + } + } else { + // It returns the map directly without any changing. + return r, nil + } + case map[int]interface{}: + recursiveOption := option + recursiveOption.Tags = newTags + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: v, + RecursiveType: recursive, + RecursiveOption: recursive == RecursiveTypeTrue, + Option: recursiveOption, + }, + ) + if err != nil && !option.ContinueOnError { + return nil, err + } + } + case map[int]string: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + case map[uint]string: + for k, v := range r { + s, err := c.String(k) + if err != nil && !option.ContinueOnError { + return nil, err + } + dataMap[s] = v + } + + default: + // Not a common type, it then uses reflection for conversion. + var reflectValue reflect.Value + if v, ok := value.(reflect.Value); ok { + reflectValue = v + } else { + reflectValue = reflect.ValueOf(value) + } + reflectKind := reflectValue.Kind() + // If it is a pointer, we should find its real data type. + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + // If `value` is type of array, it converts the value of even number index as its key and + // the value of odd number index as its corresponding value, for example: + // []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"} + // []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil} + case reflect.Slice, reflect.Array: + length := reflectValue.Len() + for i := 0; i < length; i += 2 { + s, err := c.String(reflectValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + if i+1 < length { + dataMap[s] = reflectValue.Index(i + 1).Interface() + } else { + dataMap[s] = nil + } + } + case reflect.Map, reflect.Struct, reflect.Interface: + recursiveOption := option + recursiveOption.Tags = newTags + convertedValue, err := c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: true, + Value: value, + RecursiveType: recursive, + RecursiveOption: recursive == RecursiveTypeTrue, + Option: recursiveOption, + MustMapReturn: mustMapReturn, + }, + ) + if err != nil && !option.ContinueOnError { + return nil, err + } + if m, ok := convertedValue.(map[string]interface{}); ok { + return m, nil + } + return nil, nil + default: + return nil, nil + } + } + return dataMap, nil +} + +type doMapConvertForMapOrStructValueInput struct { + IsRoot bool // It returns directly if it is not root and with no recursive converting. + Value interface{} // Current operation value. + RecursiveType RecursiveType // The type from top function entry. + RecursiveOption bool // Whether convert recursively for `current` operation. + Option MapOption // Map converting option. + MustMapReturn bool // Must return map instead of Value when empty. +} + +func (c *Converter) doMapConvertForMapOrStructValue(in doMapConvertForMapOrStructValueInput) (any, error) { + if !in.IsRoot && !in.RecursiveOption { + return in.Value, nil + } + + var ( + err error + reflectValue reflect.Value + ) + if v, ok := in.Value.(reflect.Value); ok { + reflectValue = v + in.Value = v.Interface() + } else { + reflectValue = reflect.ValueOf(in.Value) + } + reflectKind := reflectValue.Kind() + // If it is a pointer, we should find its real data type. + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Map: + var ( + mapIter = reflectValue.MapRange() + dataMap = make(map[string]interface{}) + ) + for mapIter.Next() { + var ( + mapKeyValue = mapIter.Value() + mapValue interface{} + ) + switch { + case mapKeyValue.IsZero(): + if utils.CanCallIsNil(mapKeyValue) && mapKeyValue.IsNil() { + // quick check for nil value. + mapValue = nil + } else { + // in case of: + // exception recovered: reflect: call of reflect.Value.Interface on zero Value + mapValue = reflect.New(mapKeyValue.Type()).Elem().Interface() + } + default: + mapValue = mapKeyValue.Interface() + } + s, err := c.String(mapIter.Key().Interface()) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + dataMap[s], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapValue, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } + return dataMap, nil + + case reflect.Struct: + var dataMap = make(map[string]interface{}) + // Map converting interface check. + if v, ok := in.Value.(localinterface.IMapStrAny); ok { + // Value copy, in case of concurrent safety. + for mapK, mapV := range v.MapStrAny() { + if in.RecursiveOption { + dataMap[mapK], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapV, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } else { + dataMap[mapK] = mapV + } + } + if len(dataMap) > 0 { + return dataMap, nil + } + } + // Using reflect for converting. + var ( + rtField reflect.StructField + rvField reflect.Value + reflectType = reflectValue.Type() // attribute value type. + mapKey = "" // mapKey may be the tag name or the struct attribute name. + ) + for i := 0; i < reflectValue.NumField(); i++ { + rtField = reflectType.Field(i) + rvField = reflectValue.Field(i) + // Only convert the public attributes. + fieldName := rtField.Name + if !utils.IsLetterUpper(fieldName[0]) { + continue + } + mapKey = "" + fieldTag := rtField.Tag + for _, tag := range in.Option.Tags { + if mapKey = fieldTag.Get(tag); mapKey != "" { + break + } + } + if mapKey == "" { + mapKey = fieldName + } else { + // Support json tag feature: -, omitempty + mapKey = strings.TrimSpace(mapKey) + if mapKey == "-" { + continue + } + array := strings.Split(mapKey, ",") + if len(array) > 1 { + switch strings.TrimSpace(array[1]) { + case "omitempty": + if in.Option.OmitEmpty && empty.IsEmpty(rvField.Interface()) { + continue + } else { + mapKey = strings.TrimSpace(array[0]) + } + default: + mapKey = strings.TrimSpace(array[0]) + } + } + if mapKey == "" { + mapKey = fieldName + } + } + if in.RecursiveOption || rtField.Anonymous { + // Do map converting recursively. + var ( + rvAttrField = rvField + rvAttrKind = rvField.Kind() + ) + if rvAttrKind == reflect.Ptr { + rvAttrField = rvField.Elem() + rvAttrKind = rvAttrField.Kind() + } + switch rvAttrKind { + case reflect.Struct: + // Embedded struct and has no fields, just ignores it. + // Eg: gmeta.Meta + if rvAttrField.Type().NumField() == 0 { + continue + } + var ( + hasNoTag = mapKey == fieldName + // DO NOT use rvAttrField.Interface() here, + // as it might be changed from pointer to struct. + rvInterface = rvField.Interface() + ) + switch { + case hasNoTag && rtField.Anonymous: + // It means this attribute field has no tag. + // Overwrite the attribute with sub-struct attribute fields. + anonymousValue, err := c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: true, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + if m, ok := anonymousValue.(map[string]interface{}); ok { + for k, v := range m { + dataMap[k] = v + } + } else { + dataMap[mapKey] = rvInterface + } + + // It means this attribute field has desired tag. + case !hasNoTag && rtField.Anonymous: + dataMap[mapKey], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: true, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + + default: + dataMap[mapKey], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvInterface, + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } + + // The struct attribute is type of slice. + case reflect.Array, reflect.Slice: + length := rvAttrField.Len() + if length == 0 { + dataMap[mapKey] = rvAttrField.Interface() + break + } + array := make([]interface{}, length) + for arrayIndex := 0; arrayIndex < length; arrayIndex++ { + array[arrayIndex], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: rvAttrField.Index(arrayIndex).Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } + dataMap[mapKey] = array + case reflect.Map: + var ( + mapIter = rvAttrField.MapRange() + nestedMap = make(map[string]interface{}) + ) + for mapIter.Next() { + s, err := c.String(mapIter.Key().Interface()) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + nestedMap[s], err = c.doMapConvertForMapOrStructValue( + doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: mapIter.Value().Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }, + ) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } + dataMap[mapKey] = nestedMap + default: + if rvField.IsValid() { + dataMap[mapKey] = reflectValue.Field(i).Interface() + } else { + dataMap[mapKey] = nil + } + } + } else { + // No recursive map value converting + if rvField.IsValid() { + dataMap[mapKey] = reflectValue.Field(i).Interface() + } else { + dataMap[mapKey] = nil + } + } + } + if !in.MustMapReturn && len(dataMap) == 0 { + return in.Value, nil + } + return dataMap, nil + + // The given value is type of slice. + case reflect.Array, reflect.Slice: + length := reflectValue.Len() + if length == 0 { + break + } + array := make([]interface{}, reflectValue.Len()) + for i := 0; i < length; i++ { + array[i], err = c.doMapConvertForMapOrStructValue(doMapConvertForMapOrStructValueInput{ + IsRoot: false, + Value: reflectValue.Index(i).Interface(), + RecursiveType: in.RecursiveType, + RecursiveOption: in.RecursiveType == RecursiveTypeTrue, + Option: in.Option, + }) + if err != nil && !in.Option.ContinueOnError { + return nil, err + } + } + return array, nil + + default: + } + return in.Value, nil +} diff --git a/util/gconv/internal/converter/converter_maptomap.go b/util/gconv/internal/converter/converter_maptomap.go new file mode 100644 index 00000000000..2aafabca712 --- /dev/null +++ b/util/gconv/internal/converter/converter_maptomap.go @@ -0,0 +1,140 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" +) + +// MapToMap converts any map type variable `params` to another map type variable `pointer`. +// +// The parameter `params` can be any type of map, like: +// map[string]string, map[string]struct, map[string]*struct, reflect.Value, etc. +// +// The parameter `pointer` should be type of *map, like: +// map[int]string, map[string]struct, map[string]*struct, reflect.Value, etc. +// +// 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 (c *Converter) MapToMap( + params, pointer any, mapping map[string]string, option MapOption, +) (err error) { + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + ) + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Map { + m, err := c.Map(params, option) + if err != nil { + return err + } + return c.MapToMap(m, pointer, mapping, option) + } + // Empty params map, no need continue. + if paramsRv.Len() == 0 { + return nil + } + var pointerRv reflect.Value + if v, ok := pointer.(reflect.Value); ok { + pointerRv = v + } else { + pointerRv = reflect.ValueOf(pointer) + } + pointerKind := pointerRv.Kind() + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Map { + 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. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + var ( + paramsKeys = paramsRv.MapKeys() + pointerKeyType = pointerRv.Type().Key() + pointerValueType = pointerRv.Type().Elem() + pointerValueKind = pointerValueType.Kind() + dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys)) + convertOption = ConvertOption{ + StructOption: StructOption{ContinueOnError: option.ContinueOnError}, + SliceOption: SliceOption{ContinueOnError: option.ContinueOnError}, + MapOption: option, + } + ) + // Retrieve the true element type of target map. + if pointerValueKind == reflect.Ptr { + pointerValueKind = pointerValueType.Elem().Kind() + } + for _, key := range paramsKeys { + mapValue := reflect.New(pointerValueType).Elem() + switch pointerValueKind { + case reflect.Map, reflect.Struct: + structOption := StructOption{ + ParamKeyToAttrMap: mapping, + PriorityTag: "", + ContinueOnError: option.ContinueOnError, + } + if err = c.Struct(paramsRv.MapIndex(key).Interface(), mapValue, structOption); err != nil { + return err + } + default: + convertResult, err := c.doConvert( + doConvertInput{ + FromValue: paramsRv.MapIndex(key).Interface(), + ToTypeName: pointerValueType.String(), + ReferValue: mapValue, + }, + convertOption, + ) + if err != nil { + return err + } + mapValue.Set(reflect.ValueOf(convertResult)) + } + convertResult, err := c.doConvert( + doConvertInput{ + FromValue: key.Interface(), + ToTypeName: pointerKeyType.Name(), + ReferValue: reflect.New(pointerKeyType).Elem().Interface(), + }, + convertOption, + ) + if err != nil { + return err + } + var mapKey = reflect.ValueOf(convertResult) + dataMap.SetMapIndex(mapKey, mapValue) + } + pointerRv.Set(dataMap) + return nil +} diff --git a/util/gconv/internal/converter/converter_maptomaps.go b/util/gconv/internal/converter/converter_maptomaps.go new file mode 100644 index 00000000000..3420934b5b9 --- /dev/null +++ b/util/gconv/internal/converter/converter_maptomaps.go @@ -0,0 +1,122 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" +) + +// MapToMaps converts any map type variable `params` to another map slice variable `pointer`. +// +// The parameter `params` can be type of []map, []*map, []struct, []*struct. +// +// The parameter `pointer` should be type of []map, []*map. +// +// 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 (c *Converter) MapToMaps( + params any, pointer any, paramKeyToAttrMap map[string]string, option MapOption, +) (err error) { + // Params and its element type check. + var ( + paramsRv reflect.Value + paramsKind reflect.Kind + ) + if v, ok := params.(reflect.Value); ok { + paramsRv = v + } else { + paramsRv = reflect.ValueOf(params) + } + paramsKind = paramsRv.Kind() + if paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + if paramsKind != reflect.Array && paramsKind != reflect.Slice { + return gerror.NewCode( + gcode.CodeInvalidParameter, + "params should be type of slice, example: []map/[]*map/[]struct/[]*struct", + ) + } + var ( + paramsElem = paramsRv.Type().Elem() + paramsElemKind = paramsElem.Kind() + ) + if paramsElemKind == reflect.Ptr { + 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, + ) + } + // Empty slice, no need continue. + if paramsRv.Len() == 0 { + return nil + } + // Pointer and its element type check. + var ( + pointerRv = reflect.ValueOf(pointer) + pointerKind = pointerRv.Kind() + ) + for pointerKind == reflect.Ptr { + pointerRv = pointerRv.Elem() + pointerKind = pointerRv.Kind() + } + if pointerKind != reflect.Array && pointerKind != reflect.Slice { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer should be type of *[]map/*[]*map") + } + var ( + pointerElemType = pointerRv.Type().Elem() + pointerElemKind = pointerElemType.Kind() + ) + if pointerElemKind == reflect.Ptr { + pointerElemKind = pointerElemType.Elem().Kind() + } + if pointerElemKind != reflect.Map { + return gerror.NewCode(gcode.CodeInvalidParameter, "pointer element should be type of map/*map") + } + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + var ( + pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len()) + ) + for i := 0; i < paramsRv.Len(); i++ { + var item reflect.Value + if pointerElemType.Kind() == reflect.Ptr { + item = reflect.New(pointerElemType.Elem()) + if err = c.MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap, option); err != nil { + return err + } + pointerSlice.Index(i).Set(item) + } else { + item = reflect.New(pointerElemType) + if err = c.MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap, option); err != nil { + return err + } + pointerSlice.Index(i).Set(item.Elem()) + } + } + pointerRv.Set(pointerSlice) + return +} diff --git a/util/gconv/internal/converter/converter_rune.go b/util/gconv/internal/converter/converter_rune.go new file mode 100644 index 00000000000..e7ea9d5febd --- /dev/null +++ b/util/gconv/internal/converter/converter_rune.go @@ -0,0 +1,31 @@ +// 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 converter + +// Rune converts `any` to rune. +func (c *Converter) Rune(any any) (rune, error) { + if v, ok := any.(rune); ok { + return v, nil + } + v, err := c.Int32(any) + if err != nil { + return 0, err + } + return v, nil +} + +// Runes converts `any` to []rune. +func (c *Converter) Runes(any any) ([]rune, error) { + if v, ok := any.([]rune); ok { + return v, nil + } + s, err := c.String(any) + if err != nil { + return nil, err + } + return []rune(s), nil +} diff --git a/util/gconv/internal/converter/converter_scan.go b/util/gconv/internal/converter/converter_scan.go new file mode 100644 index 00000000000..b580e9abedb --- /dev/null +++ b/util/gconv/internal/converter/converter_scan.go @@ -0,0 +1,395 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// ScanOption is the option for the Scan function. +type ScanOption struct { + // ParamKeyToAttrMap specifies the mapping between parameter keys and struct attribute names. + ParamKeyToAttrMap map[string]string + + // ContinueOnError specifies whether to continue converting the next element + // if one element converting fails. + ContinueOnError bool +} + +// Scan automatically checks the type of `pointer` and converts `params` to `pointer`. +func (c *Converter) Scan(srcValue any, dstPointer any, option ScanOption) (err error) { + // Check if srcValue is nil, in which case no conversion is needed + if srcValue == nil { + return nil + } + // Check if dstPointer is nil, which is an invalid parameter + if dstPointer == nil { + return gerror.NewCode( + gcode.CodeInvalidParameter, + `destination pointer should not be nil`, + ) + } + + // Get the reflection type and value of dstPointer + var ( + 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) + } + + // Validate the kind of dstPointer + var dstPointerReflectKind = dstPointerReflectType.Kind() + if dstPointerReflectKind != reflect.Ptr { + // If dstPointer is not a pointer, try to get its address + if dstPointerReflectValue.CanAddr() { + dstPointerReflectValue = dstPointerReflectValue.Addr() + dstPointerReflectType = dstPointerReflectValue.Type() + dstPointerReflectKind = dstPointerReflectType.Kind() + } else { + // If dstPointer is not a pointer and cannot be addressed, return an error + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `destination pointer should be type of pointer, but got type: %v`, + dstPointerReflectType, + ) + } + } + + // Get the reflection value of srcValue + var srcValueReflectValue reflect.Value + if v, ok := srcValue.(reflect.Value); ok { + srcValueReflectValue = v + } else { + srcValueReflectValue = reflect.ValueOf(srcValue) + } + + // Get the element type and kind of dstPointer + var ( + dstPointerReflectValueElem = dstPointerReflectValue.Elem() + dstPointerReflectValueElemKind = dstPointerReflectValueElem.Kind() + ) + // Handle multiple level pointers + if dstPointerReflectValueElemKind == reflect.Ptr { + if dstPointerReflectValueElem.IsNil() { + // Create a new value for the pointer dereference + nextLevelPtr := reflect.New(dstPointerReflectValueElem.Type().Elem()) + // Recursively scan into the dereferenced pointer + if err = c.Scan(srcValueReflectValue, nextLevelPtr, option); err == nil { + dstPointerReflectValueElem.Set(nextLevelPtr) + } + return + } + return c.Scan(srcValueReflectValue, dstPointerReflectValueElem, option) + } + + // Check if srcValue and dstPointer are the same type, in which case direct assignment can be performed + if ok := c.doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem); ok { + return nil + } + + // Handle different destination types + switch dstPointerReflectValueElemKind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v, err := c.Int64(srcValue) + if err != nil && !option.ContinueOnError { + return err + } + dstPointerReflectValueElem.SetInt(v) + return nil + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v, err := c.Uint64(srcValue) + if err != nil && !option.ContinueOnError { + return err + } + dstPointerReflectValueElem.SetUint(v) + return nil + + case reflect.Float32, reflect.Float64: + v, err := c.Float64(srcValue) + if err != nil && !option.ContinueOnError { + return err + } + dstPointerReflectValueElem.SetFloat(v) + return nil + + case reflect.String: + v, err := c.String(srcValue) + if err != nil && !option.ContinueOnError { + return err + } + dstPointerReflectValueElem.SetString(v) + return nil + + case reflect.Bool: + v, err := c.Bool(srcValue) + if err != nil && !option.ContinueOnError { + return err + } + dstPointerReflectValueElem.SetBool(v) + return nil + + case reflect.Slice: + // Handle slice type conversion + var ( + dstElemType = dstPointerReflectValueElem.Type().Elem() + dstElemKind = dstElemType.Kind() + ) + // The slice element might be a pointer type + if dstElemKind == reflect.Ptr { + dstElemType = dstElemType.Elem() + dstElemKind = dstElemType.Kind() + } + // Special handling for struct or map slice elements + if dstElemKind == reflect.Struct || dstElemKind == reflect.Map { + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, option) + } + // Handle basic type slice conversions + var srcValueReflectValueKind = srcValueReflectValue.Kind() + if srcValueReflectValueKind == reflect.Slice || srcValueReflectValueKind == reflect.Array { + var ( + srcLen = srcValueReflectValue.Len() + newSlice = reflect.MakeSlice(dstPointerReflectValueElem.Type(), srcLen, srcLen) + ) + for i := 0; i < srcLen; i++ { + srcElem := srcValueReflectValue.Index(i).Interface() + switch dstElemType.Kind() { + case reflect.String: + v, err := c.String(srcElem) + if err != nil && !option.ContinueOnError { + return err + } + newSlice.Index(i).SetString(v) + case reflect.Int: + v, err := c.Int64(srcElem) + if err != nil && !option.ContinueOnError { + return err + } + newSlice.Index(i).SetInt(v) + case reflect.Int64: + v, err := c.Int64(srcElem) + if err != nil && !option.ContinueOnError { + return err + } + newSlice.Index(i).SetInt(v) + case reflect.Float64: + v, err := c.Float64(srcElem) + if err != nil && !option.ContinueOnError { + return err + } + newSlice.Index(i).SetFloat(v) + case reflect.Bool: + v, err := c.Bool(srcElem) + if err != nil && !option.ContinueOnError { + return err + } + newSlice.Index(i).SetBool(v) + default: + return c.Scan( + srcElem, newSlice.Index(i).Addr().Interface(), option, + ) + } + } + dstPointerReflectValueElem.Set(newSlice) + return nil + } + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, option) + + default: + // Handle complex types (structs, maps, etc.) + return c.doScanForComplicatedTypes(srcValue, dstPointer, dstPointerReflectType, option) + } +} + +// doScanForComplicatedTypes handles the scanning of complex data types. +// It supports converting between maps, structs, and slices of these types. +// The function first attempts JSON conversion, then falls back to specific type handling. +// +// It supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting. +// +// Parameters: +// - srcValue: The source value to convert from +// - dstPointer: The destination pointer to convert to +// - dstPointerReflectType: The reflection type of the destination pointer +// - paramKeyToAttrMap: Optional mapping between parameter keys and struct attribute names +func (c *Converter) doScanForComplicatedTypes( + srcValue, dstPointer any, + dstPointerReflectType reflect.Type, + option ScanOption, +) error { + // Try JSON conversion first + ok, err := c.doConvertWithJsonCheck(srcValue, dstPointer) + if err != nil { + return err + } + if ok { + return nil + } + + // Handle specific type conversions + var ( + dstPointerReflectTypeElem = dstPointerReflectType.Elem() + dstPointerReflectTypeElemKind = dstPointerReflectTypeElem.Kind() + keyToAttributeNameMapping = option.ParamKeyToAttrMap + ) + // Handle different destination types + switch dstPointerReflectTypeElemKind { + case reflect.Map: + // Convert map to map + return c.MapToMap(srcValue, dstPointer, keyToAttributeNameMapping, MapOption{ + ContinueOnError: option.ContinueOnError, + }) + + case reflect.Array, reflect.Slice: + var ( + sliceElem = dstPointerReflectTypeElem.Elem() + sliceElemKind = sliceElem.Kind() + ) + // Handle pointer elements + for sliceElemKind == reflect.Ptr { + sliceElem = sliceElem.Elem() + sliceElemKind = sliceElem.Kind() + } + if sliceElemKind == reflect.Map { + // Convert to slice of maps + return c.MapToMaps(srcValue, dstPointer, keyToAttributeNameMapping, MapOption{ + ContinueOnError: option.ContinueOnError, + }) + } + // Convert to slice of structs + var ( + sliceOption = SliceOption{ + ContinueOnError: option.ContinueOnError, + } + mapOption = StructOption{ + ParamKeyToAttrMap: keyToAttributeNameMapping, + ContinueOnError: option.ContinueOnError, + } + ) + return c.Structs(srcValue, dstPointer, sliceOption, mapOption) + + default: + structOption := StructOption{ + ParamKeyToAttrMap: keyToAttributeNameMapping, + PriorityTag: "", + ContinueOnError: option.ContinueOnError, + } + return c.Struct(srcValue, dstPointer, structOption) + } +} + +// doConvertWithTypeCheck supports `pointer` in type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` +// for converting. +func (c *Converter) doConvertWithTypeCheck(srcValueReflectValue, dstPointerReflectValueElem reflect.Value) (ok bool) { + if !dstPointerReflectValueElem.IsValid() || !srcValueReflectValue.IsValid() { + return false + } + switch { + // Examples: + // UploadFile => UploadFile + // []UploadFile => []UploadFile + // *UploadFile => *UploadFile + // *[]UploadFile => *[]UploadFile + // map[int][int] => map[int][int] + // []map[int][int] => []map[int][int] + // *[]map[int][int] => *[]map[int][int] + case dstPointerReflectValueElem.Type() == srcValueReflectValue.Type(): + dstPointerReflectValueElem.Set(srcValueReflectValue) + return true + + // Examples: + // UploadFile => *UploadFile + // []UploadFile => *[]UploadFile + // map[int][int] => *map[int][int] + // []map[int][int] => *[]map[int][int] + case dstPointerReflectValueElem.Kind() == reflect.Ptr && + dstPointerReflectValueElem.Elem().IsValid() && + dstPointerReflectValueElem.Elem().Type() == srcValueReflectValue.Type(): + dstPointerReflectValueElem.Elem().Set(srcValueReflectValue) + return true + + // Examples: + // *UploadFile => UploadFile + // *[]UploadFile => []UploadFile + // *map[int][int] => map[int][int] + // *[]map[int][int] => []map[int][int] + case srcValueReflectValue.Kind() == reflect.Ptr && + srcValueReflectValue.Elem().IsValid() && + dstPointerReflectValueElem.Type() == srcValueReflectValue.Elem().Type(): + dstPointerReflectValueElem.Set(srcValueReflectValue.Elem()) + return true + + default: + return false + } +} + +// doConvertWithJsonCheck attempts to convert the source value to the destination +// using JSON marshaling and unmarshaling. This is particularly useful for complex +// types that can be represented as JSON. +// +// Parameters: +// - srcValue: The source value to convert from +// - dstPointer: The destination pointer to convert to +// +// Returns: +// - bool: true if JSON conversion was successful +// - error: any error that occurred during conversion +func (c *Converter) doConvertWithJsonCheck(srcValue any, dstPointer any) (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 + } + return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Interface()) + } else if dstPointerReflectType.CanAddr() { + return true, json.UnmarshalUseNumber(valueResult, dstPointerReflectType.Addr().Interface()) + } + } else { + return true, json.UnmarshalUseNumber(valueResult, dstPointer) + } + } + + 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 + } + return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Interface()) + } else if dstPointerReflectType.CanAddr() { + return true, json.UnmarshalUseNumber(valueBytes, dstPointerReflectType.Addr().Interface()) + } + } else { + return true, json.UnmarshalUseNumber(valueBytes, dstPointer) + } + } + + default: + // The `params` might be struct that implements interface function Interface, eg: gvar.Var. + if v, ok := srcValue.(localinterface.IInterface); ok { + return c.doConvertWithJsonCheck(v.Interface(), dstPointer) + } + } + return false, nil +} diff --git a/util/gconv/internal/converter/converter_slice_any.go b/util/gconv/internal/converter/converter_slice_any.go new file mode 100644 index 00000000000..8a0402ec1cb --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_any.go @@ -0,0 +1,143 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// SliceOption is the option for Slice type converting. +type SliceOption struct { + // ContinueOnError specifies whether to continue converting the next element + // if one element converting fails. + ContinueOnError bool +} + +// SliceAny converts `any` to []any. +func (c *Converter) SliceAny(any interface{}, option SliceOption) ([]any, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + array []interface{} + ) + switch value := any.(type) { + case []interface{}: + array = value + case []string: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []int: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []int8: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []int16: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []int32: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []int64: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []uint: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + + case []uint16: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []uint32: + for _, v := range value { + array = append(array, v) + } + case []uint64: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []bool: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []float32: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + case []float64: + array = make([]interface{}, len(value)) + for k, v := range value { + array[k] = v + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IInterfaces); ok { + return v.Interfaces(), err + } + + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]interface{}, length) + ) + for i := 0; i < length; i++ { + slice[i] = originValueAndKind.OriginValue.Index(i).Interface() + } + return slice, err + + default: + return []interface{}{any}, err + } +} diff --git a/util/gconv/internal/converter/converter_slice_float.go b/util/gconv/internal/converter/converter_slice_float.go new file mode 100644 index 00000000000..27d59c6abbc --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_float.go @@ -0,0 +1,417 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// SliceFloat32 converts `any` to []float32. +func (c *Converter) SliceFloat32(any interface{}, option SliceOption) ([]float32, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + f float32 + array []float32 = nil + ) + switch value := any.(type) { + case []string: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int8: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int16: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int32: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int64: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []float32{}, err + } + if utils.IsNumeric(value) { + f, err = c.Float32(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []float32{f}, err + } + case []uint16: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint32: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint64: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []bool: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []float32: + array = value + case []float64: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []interface{}: + array = make([]float32, len(value)) + for k, v := range value { + f, err = c.Float32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IFloats); ok { + return c.SliceFloat32(v.Floats(), option) + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceFloat32(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]float32, length) + ) + for i := 0; i < length; i++ { + f, err = c.Float32(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = f + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []float32{}, err + } + f, err = c.Float32(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []float32{f}, err + } +} + +// SliceFloat64 converts `any` to []float64. +func (c *Converter) SliceFloat64(any interface{}, option SliceOption) ([]float64, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + f float64 + array []float64 = nil + ) + switch value := any.(type) { + case []string: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int8: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int16: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int32: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []int64: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []float64{}, err + } + if utils.IsNumeric(value) { + f, err = c.Float64(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []float64{f}, err + } + case []uint16: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint32: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []uint64: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []bool: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []float32: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + case []float64: + array = value + case []interface{}: + array = make([]float64, len(value)) + for k, v := range value { + f, err = c.Float64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = f + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IFloats); ok { + return v.Floats(), err + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceFloat64(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]float64, length) + ) + for i := 0; i < length; i++ { + f, err = c.Float64(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = f + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []float64{}, err + } + f, err = c.Float64(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []float64{f}, err + } +} diff --git a/util/gconv/internal/converter/converter_slice_int.go b/util/gconv/internal/converter/converter_slice_int.go new file mode 100644 index 00000000000..b1a769d275a --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_int.go @@ -0,0 +1,536 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// SliceInt converts `any` to []int. +func (c *Converter) SliceInt(any any, option SliceOption) ([]int, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ii int + array []int = nil + ) + switch value := any.(type) { + case []string: + array = make([]int, len(value)) + for k, v := range value { + ii, err = c.Int(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []int: + array = value + case []int8: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []int16: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []int32: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []int64: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []uint: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []int{}, err + } + if utils.IsNumeric(value) { + ii, err = c.Int(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int{ii}, err + } + case []uint16: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []uint32: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []uint64: + array = make([]int, len(value)) + for k, v := range value { + array[k] = int(v) + } + case []bool: + array = make([]int, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]int, len(value)) + for k, v := range value { + ii, err = c.Int(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []float64: + array = make([]int, len(value)) + for k, v := range value { + ii, err = c.Int(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []interface{}: + array = make([]int, len(value)) + for k, v := range value { + ii, err = c.Int(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case [][]byte: + array = make([]int, len(value)) + for k, v := range value { + ii, err = c.Int(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IInts); ok { + return v.Ints(), err + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceInt(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]int, length) + ) + for i := 0; i < length; i++ { + ii, err = c.Int(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ii + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []int{}, err + } + ii, err = c.Int(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int{ii}, err + } +} + +// SliceInt32 converts `any` to []int32. +func (c *Converter) SliceInt32(any any, option SliceOption) ([]int32, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ii int32 + array []int32 = nil + ) + switch value := any.(type) { + case []string: + array = make([]int32, len(value)) + for k, v := range value { + ii, err = c.Int32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []int: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []int8: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []int16: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []int32: + array = value + case []int64: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []uint: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []int32{}, err + } + if utils.IsNumeric(value) { + ii, err = c.Int32(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int32{ii}, err + } + case []uint16: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []uint32: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []uint64: + array = make([]int32, len(value)) + for k, v := range value { + array[k] = int32(v) + } + case []bool: + array = make([]int32, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]int32, len(value)) + for k, v := range value { + ii, err = c.Int32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []float64: + array = make([]int32, len(value)) + for k, v := range value { + ii, err = c.Int32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []interface{}: + array = make([]int32, len(value)) + for k, v := range value { + ii, err = c.Int32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case [][]byte: + array = make([]int32, len(value)) + for k, v := range value { + ii, err = c.Int32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IInts); ok { + return c.SliceInt32(v.Ints(), option) + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceInt32(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]int32, length) + ) + for i := 0; i < length; i++ { + ii, err = c.Int32(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ii + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []int32{}, err + } + ii, err = c.Int32(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int32{ii}, err + } +} + +// SliceInt64 converts `any` to []int64. +func (c *Converter) SliceInt64(any any, option SliceOption) ([]int64, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ii int64 + array []int64 = nil + ) + switch value := any.(type) { + case []string: + array = make([]int64, len(value)) + for k, v := range value { + ii, err = c.Int64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []int: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []int8: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []int16: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []int32: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []int64: + array = value + case []uint: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []int64{}, err + } + if utils.IsNumeric(value) { + ii, err = c.Int64(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int64{ii}, err + } + case []uint16: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []uint32: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []uint64: + array = make([]int64, len(value)) + for k, v := range value { + array[k] = int64(v) + } + case []bool: + array = make([]int64, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]int64, len(value)) + for k, v := range value { + ii, err = c.Int64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []float64: + array = make([]int64, len(value)) + for k, v := range value { + ii, err = c.Int64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case []interface{}: + array = make([]int64, len(value)) + for k, v := range value { + ii, err = c.Int64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + case [][]byte: + array = make([]int64, len(value)) + for k, v := range value { + ii, err = c.Int64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ii + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IInts); ok { + return c.SliceInt64(v.Ints(), option) + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceInt64(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]int64, length) + ) + for i := 0; i < length; i++ { + ii, err = c.Int64(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ii + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []int64{}, err + } + ii, err = c.Int64(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []int64{ii}, err + } +} diff --git a/util/gconv/internal/converter/converter_slice_map.go b/util/gconv/internal/converter/converter_slice_map.go new file mode 100644 index 00000000000..837f51ab945 --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_map.go @@ -0,0 +1,59 @@ +// 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 converter + +import "github.com/gogf/gf/v2/internal/json" + +// SliceMap converts `value` to []map[string]any. +// Note that it automatically checks and converts json string to []map if `value` is string/[]byte. +func (c *Converter) SliceMap(value any, sliceOption SliceOption, mapOption MapOption) ([]map[string]any, error) { + if value == nil { + return nil, nil + } + switch r := value.(type) { + case string: + list := make([]map[string]any, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber([]byte(r), &list); err != nil { + return nil, err + } + return list, nil + } + return nil, nil + + case []byte: + list := make([]map[string]any, 0) + if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' { + if err := json.UnmarshalUseNumber(r, &list); err != nil { + return nil, err + } + return list, nil + } + return nil, nil + + case []map[string]any: + return r, nil + + default: + array, err := c.SliceAny(value, sliceOption) + if err != nil { + return nil, err + } + if len(array) == 0 { + return nil, nil + } + list := make([]map[string]any, len(array)) + for k, v := range array { + m, err := c.Map(v, mapOption) + if err != nil && !sliceOption.ContinueOnError { + return nil, err + } + list[k] = m + } + return list, nil + } +} diff --git a/util/gconv/internal/converter/converter_slice_str.go b/util/gconv/internal/converter/converter_slice_str.go new file mode 100644 index 00000000000..e95f70b8a27 --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_str.go @@ -0,0 +1,220 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// SliceStr converts `any` to []string. +func (c *Converter) SliceStr(any interface{}, option SliceOption) ([]string, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + s string + array []string = nil + ) + switch value := any.(type) { + case []int: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []int8: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []int16: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []int32: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []int64: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []uint: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + return array, err + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []string{}, err + } + return []string{value}, err + case []uint16: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []uint32: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []uint64: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []bool: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []float32: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []float64: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []interface{}: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + case []string: + array = value + case [][]byte: + array = make([]string, len(value)) + for k, v := range value { + s, err = c.String(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = s + } + } + if array != nil { + return array, err + } + if v, ok := any.(localinterface.IStrings); ok { + return v.Strings(), err + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceStr(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]string, length) + ) + for i := 0; i < length; i++ { + s, err = c.String(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = s + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []string{}, err + } + s, err = c.String(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []string{s}, err + } +} diff --git a/util/gconv/internal/converter/converter_slice_uint.go b/util/gconv/internal/converter/converter_slice_uint.go new file mode 100644 index 00000000000..173c96469cd --- /dev/null +++ b/util/gconv/internal/converter/converter_slice_uint.go @@ -0,0 +1,527 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/reflection" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// SliceUint converts `any` to []uint. +func (c *Converter) SliceUint(any interface{}, option SliceOption) ([]uint, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ui uint + array []uint = nil + ) + switch value := any.(type) { + case []string: + array = make([]uint, len(value)) + for k, v := range value { + ui, err = c.Uint(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []int8: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []int16: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []int32: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []int64: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []uint: + array = value + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []uint{}, err + } + if utils.IsNumeric(value) { + ui, err = c.Uint(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint{ui}, err + } + case []uint16: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []uint32: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []uint64: + array = make([]uint, len(value)) + for k, v := range value { + array[k] = uint(v) + } + case []bool: + array = make([]uint, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]uint, len(value)) + for k, v := range value { + ui, err = c.Uint(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []float64: + array = make([]uint, len(value)) + for k, v := range value { + ui, err = c.Uint(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []interface{}: + array = make([]uint, len(value)) + for k, v := range value { + ui, err = c.Uint(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case [][]byte: + array = make([]uint, len(value)) + for k, v := range value { + ui, err = c.Uint(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + } + + if array != nil { + return array, err + } + + // Default handler. + if v, ok := any.(localinterface.IUints); ok { + return v.Uints(), err + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceUint(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]uint, length) + ) + for i := 0; i < length; i++ { + ui, err = c.Uint(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ui + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []uint{}, err + } + ui, err = c.Uint(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint{ui}, err + } +} + +// SliceUint32 converts `any` to []uint32. +func (c *Converter) SliceUint32(any interface{}, option SliceOption) ([]uint32, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ui uint32 + array []uint32 = nil + ) + switch value := any.(type) { + case []string: + array = make([]uint32, len(value)) + for k, v := range value { + ui, err = c.Uint32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []int8: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []int16: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []int32: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []int64: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []uint: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []uint32{}, err + } + if utils.IsNumeric(value) { + ui, err = c.Uint32(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint32{ui}, err + } + case []uint16: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []uint32: + array = value + case []uint64: + array = make([]uint32, len(value)) + for k, v := range value { + array[k] = uint32(v) + } + case []bool: + array = make([]uint32, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]uint32, len(value)) + for k, v := range value { + ui, err = c.Uint32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []float64: + array = make([]uint32, len(value)) + for k, v := range value { + ui, err = c.Uint32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []interface{}: + array = make([]uint32, len(value)) + for k, v := range value { + ui, err = c.Uint32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case [][]byte: + array = make([]uint32, len(value)) + for k, v := range value { + ui, err = c.Uint32(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + } + if array != nil { + return array, err + } + + // Default handler. + if v, ok := any.(localinterface.IUints); ok { + return c.SliceUint32(v.Uints(), option) + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceUint32(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]uint32, length) + ) + for i := 0; i < length; i++ { + ui, err = c.Uint32(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ui + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []uint32{}, err + } + ui, err = c.Uint32(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint32{ui}, err + } +} + +// SliceUint64 converts `any` to []uint64. +func (c *Converter) SliceUint64(any interface{}, option SliceOption) ([]uint64, error) { + if empty.IsNil(any) { + return nil, nil + } + var ( + err error + ui uint64 + array []uint64 = nil + ) + switch value := any.(type) { + case []string: + array = make([]uint64, len(value)) + for k, v := range value { + ui, err = c.Uint64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []int8: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []int16: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []int32: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []int64: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []uint: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []uint8: + if json.Valid(value) { + if err = json.UnmarshalUseNumber(value, &array); array != nil { + return array, err + } + } + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case string: + byteValue := []byte(value) + if json.Valid(byteValue) { + if err = json.UnmarshalUseNumber(byteValue, &array); array != nil { + return array, err + } + } + if value == "" { + return []uint64{}, err + } + if utils.IsNumeric(value) { + ui, err = c.Uint64(value) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint64{ui}, err + } + case []uint16: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []uint32: + array = make([]uint64, len(value)) + for k, v := range value { + array[k] = uint64(v) + } + case []uint64: + array = value + case []bool: + array = make([]uint64, len(value)) + for k, v := range value { + if v { + array[k] = 1 + } else { + array[k] = 0 + } + } + case []float32: + array = make([]uint64, len(value)) + for k, v := range value { + ui, err = c.Uint64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []float64: + array = make([]uint64, len(value)) + for k, v := range value { + ui, err = c.Uint64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case []interface{}: + array = make([]uint64, len(value)) + for k, v := range value { + ui, err = c.Uint64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + case [][]byte: + array = make([]uint64, len(value)) + for k, v := range value { + ui, err = c.Uint64(v) + if err != nil && !option.ContinueOnError { + return nil, err + } + array[k] = ui + } + } + if array != nil { + return array, err + } + // Default handler. + if v, ok := any.(localinterface.IUints); ok { + return c.SliceUint64(v.Uints(), option) + } + if v, ok := any.(localinterface.IInterfaces); ok { + return c.SliceUint64(v.Interfaces(), option) + } + // Not a common type, it then uses reflection for conversion. + originValueAndKind := reflection.OriginValueAndKind(any) + switch originValueAndKind.OriginKind { + case reflect.Slice, reflect.Array: + var ( + length = originValueAndKind.OriginValue.Len() + slice = make([]uint64, length) + ) + for i := 0; i < length; i++ { + ui, err = c.Uint64(originValueAndKind.OriginValue.Index(i).Interface()) + if err != nil && !option.ContinueOnError { + return nil, err + } + slice[i] = ui + } + return slice, err + + default: + if originValueAndKind.OriginValue.IsZero() { + return []uint64{}, err + } + ui, err = c.Uint64(any) + if err != nil && !option.ContinueOnError { + return nil, err + } + return []uint64{ui}, err + } +} diff --git a/util/gconv/internal/converter/converter_string.go b/util/gconv/internal/converter/converter_string.go new file mode 100644 index 00000000000..cd9434faaf6 --- /dev/null +++ b/util/gconv/internal/converter/converter_string.go @@ -0,0 +1,135 @@ +// 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 converter + +import ( + "fmt" + "reflect" + "strconv" + "time" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +func (c *Converter) String(any any) (string, error) { + if empty.IsNil(any) { + return "", nil + } + switch value := any.(type) { + case int: + return strconv.Itoa(value), nil + case int8: + return strconv.Itoa(int(value)), nil + case int16: + return strconv.Itoa(int(value)), nil + case int32: + return strconv.Itoa(int(value)), nil + case int64: + return strconv.FormatInt(value, 10), nil + case uint: + return strconv.FormatUint(uint64(value), 10), nil + case uint8: + return strconv.FormatUint(uint64(value), 10), nil + case uint16: + return strconv.FormatUint(uint64(value), 10), nil + case uint32: + return strconv.FormatUint(uint64(value), 10), nil + case uint64: + return strconv.FormatUint(value, 10), nil + case float32: + return strconv.FormatFloat(float64(value), 'f', -1, 32), nil + case float64: + return strconv.FormatFloat(value, 'f', -1, 64), nil + case bool: + return strconv.FormatBool(value), nil + case string: + return value, nil + case []byte: + return string(value), nil + case complex64, complex128: + return fmt.Sprintf("%v", value), nil + case time.Time: + if value.IsZero() { + return "", nil + } + return value.String(), nil + case *time.Time: + if value == nil { + return "", nil + } + return value.String(), nil + case gtime.Time: + if value.IsZero() { + return "", nil + } + return value.String(), nil + case *gtime.Time: + if value == nil { + return "", nil + } + return value.String(), nil + default: + if f, ok := value.(localinterface.IString); ok { + // If the variable implements the String() interface, + // then use that interface to perform the conversion + return f.String(), nil + } + if f, ok := value.(localinterface.IError); ok { + // If the variable implements the Error() interface, + // then use that interface to perform the conversion + return f.Error(), nil + } + // Reflect checks. + var ( + rv = reflect.ValueOf(value) + kind = rv.Kind() + ) + switch kind { + case + reflect.Chan, + reflect.Map, + reflect.Slice, + reflect.Func, + reflect.Interface, + reflect.UnsafePointer: + if rv.IsNil() { + return "", nil + } + case reflect.String: + return rv.String(), nil + case reflect.Ptr: + if rv.IsNil() { + return "", nil + } + return c.String(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10), nil + case reflect.Uintptr: + return strconv.FormatUint(rv.Uint(), 10), nil + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'f', -1, 64), nil + case reflect.Bool: + return strconv.FormatBool(rv.Bool()), nil + default: + } + // Finally, we use json.Marshal to convert. + jsonContent, err := json.Marshal(value) + if err != nil { + return fmt.Sprint(value), gerror.WrapCodef( + gcode.CodeInvalidParameter, err, "error marshaling value to JSON for: %v", value, + ) + } + return string(jsonContent), nil + } +} diff --git a/util/gconv/internal/converter/converter_struct.go b/util/gconv/internal/converter/converter_struct.go new file mode 100644 index 00000000000..6bf8c6913d1 --- /dev/null +++ b/util/gconv/internal/converter/converter_struct.go @@ -0,0 +1,662 @@ +// 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 converter + +import ( + "reflect" + "strings" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" + "github.com/gogf/gf/v2/util/gconv/internal/structcache" +) + +// StructOption is the option for Struct converting. +type StructOption struct { + // ParamKeyToAttrMap is the map for custom parameter key to attribute name mapping. + ParamKeyToAttrMap map[string]string + + // PriorityTag is the priority tag for struct converting. + PriorityTag string + + // ContinueOnError specifies whether to continue converting the next element + // if one element converting fails. + ContinueOnError bool +} + +// Struct is the core internal converting function for any data to struct. +func (c *Converter) Struct(params, pointer any, option StructOption) (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") + } + + // JSON content converting. + ok, err := c.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 { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + + var ( + paramsReflectValue reflect.Value + paramsInterface any // DO NOT use `params` directly as it might be type `reflect.Value` + pointerReflectValue reflect.Value + pointerReflectKind reflect.Kind + pointerElemReflectValue reflect.Value // The reflection value to struct element. + ) + if v, ok := params.(reflect.Value); ok { + paramsReflectValue = v + } else { + paramsReflectValue = reflect.ValueOf(params) + } + paramsInterface = paramsReflectValue.Interface() + if v, ok := pointer.(reflect.Value); ok { + pointerReflectValue = v + pointerElemReflectValue = v + } else { + pointerReflectValue = reflect.ValueOf(pointer) + pointerReflectKind = pointerReflectValue.Kind() + if pointerReflectKind != reflect.Ptr { + return gerror.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, + "destination pointer cannot be nil", + ) + } + pointerElemReflectValue = pointerReflectValue.Elem() + } + + // If `params` and `pointer` are the same type, the do directly assignment. + // For performance enhancement purpose. + if ok = c.doConvertWithTypeCheck(paramsReflectValue, pointerElemReflectValue); ok { + return nil + } + + // custom convert. + if ok, err = c.callCustomConverter(paramsReflectValue, pointerReflectValue); ok { + return err + } + + // Normal unmarshalling interfaces checks. + if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerReflectValue, paramsInterface); ok { + return err + } + + // It automatically creates struct object if necessary. + // For example, if `pointer` is **User, then `elem` is *User, which is a pointer to User. + if pointerElemReflectValue.Kind() == reflect.Ptr { + if !pointerElemReflectValue.IsValid() || pointerElemReflectValue.IsNil() { + e := reflect.New(pointerElemReflectValue.Type().Elem()) + pointerElemReflectValue.Set(e) + defer func() { + if err != nil { + // If it is converted failed, it reset the `pointer` to nil. + pointerReflectValue.Elem().Set(reflect.Zero(pointerReflectValue.Type().Elem())) + } + }() + } + // if v, ok := pointerElemReflectValue.Interface().(localinterface.IUnmarshalValue); ok { + // return v.UnmarshalValue(params) + // } + // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. + if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { + return err + } + // Retrieve its element, may be struct at last. + pointerElemReflectValue = pointerElemReflectValue.Elem() + } + paramsMap, ok := paramsInterface.(map[string]any) + if !ok { + // paramsMap is the map[string]any type variable for params. + // DO NOT use MapDeep here. + paramsMap, err = c.doMapConvert(paramsInterface, RecursiveTypeAuto, true, MapOption{ + ContinueOnError: option.ContinueOnError, + }) + if err != nil { + return err + } + if paramsMap == nil { + return gerror.NewCodef( + gcode.CodeInvalidParameter, + `convert params from "%#v" to "map[string]any" failed`, + params, + ) + } + } + // Nothing to be done as the parameters are empty. + if len(paramsMap) == 0 { + return nil + } + // Get struct info from cache or parse struct and cache the struct info. + cachedStructInfo := c.internalConverter.GetCachedStructInfo( + pointerElemReflectValue.Type(), option.PriorityTag, + ) + // Nothing to be converted. + if cachedStructInfo == nil { + return nil + } + // For the structure types of 0 tagOrFiledNameToFieldInfoMap, + // they also need to be cached to prevent invalid logic + if cachedStructInfo.HasNoFields() { + return nil + } + var ( + // Indicates that those values have been used and cannot be reused. + usedParamsKeyOrTagNameMap = structcache.GetUsedParamsKeyOrTagNameMapFromPool() + cachedFieldInfo *structcache.CachedFieldInfo + paramsValue any + ) + defer structcache.PutUsedParamsKeyOrTagNameMapToPool(usedParamsKeyOrTagNameMap) + + // Firstly, search according to custom mapping rules. + // If a possible direct assignment is found, reduce the number of subsequent map searches. + for paramKey, fieldName := range option.ParamKeyToAttrMap { + paramsValue, ok = paramsMap[paramKey] + if !ok { + continue + } + cachedFieldInfo = cachedStructInfo.GetFieldInfo(fieldName) + if cachedFieldInfo != nil { + fieldValue := cachedFieldInfo.GetFieldReflectValueFrom(pointerElemReflectValue) + if err = c.bindVarToStructField( + cachedFieldInfo, + fieldValue, + paramsValue, + option, + ); err != nil { + return err + } + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramsValue, pointerReflectValue, option, + ); err != nil { + return err + } + } + usedParamsKeyOrTagNameMap[paramKey] = struct{}{} + } + } + // Already done converting for given `paramsMap`. + if len(usedParamsKeyOrTagNameMap) == len(paramsMap) { + return nil + } + return c.bindStructWithLoopFieldInfos( + paramsMap, pointerElemReflectValue, + usedParamsKeyOrTagNameMap, cachedStructInfo, + option, + ) +} + +func (c *Converter) setOtherSameNameField( + cachedFieldInfo *structcache.CachedFieldInfo, + srcValue any, + structValue reflect.Value, + option StructOption, +) (err error) { + // loop the same field name of all sub attributes. + for _, otherFieldInfo := range cachedFieldInfo.OtherSameNameField { + fieldValue := cachedFieldInfo.GetOtherFieldReflectValueFrom(structValue, otherFieldInfo.FieldIndexes) + if err = c.bindVarToStructField(otherFieldInfo, fieldValue, srcValue, option); err != nil { + return err + } + } + return nil +} + +func (c *Converter) bindStructWithLoopFieldInfos( + paramsMap map[string]any, + structValue reflect.Value, + usedParamsKeyOrTagNameMap map[string]struct{}, + cachedStructInfo *structcache.CachedStructInfo, + option StructOption, +) (err error) { + var ( + cachedFieldInfo *structcache.CachedFieldInfo + fuzzLastKey string + fieldValue reflect.Value + paramKey string + paramValue any + matched bool + ok bool + ) + for _, cachedFieldInfo = range cachedStructInfo.GetFieldConvertInfos() { + for _, fieldTag := range cachedFieldInfo.PriorityTagAndFieldName { + if paramValue, ok = paramsMap[fieldTag]; !ok { + continue + } + fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) + if err = c.bindVarToStructField( + cachedFieldInfo, fieldValue, paramValue, option, + ); err != nil && !option.ContinueOnError { + return err + } + // handle same field name in nested struct. + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramValue, structValue, option, + ); err != nil && !option.ContinueOnError { + return err + } + } + usedParamsKeyOrTagNameMap[fieldTag] = struct{}{} + matched = true + break + } + if matched { + matched = false + continue + } + + fuzzLastKey = cachedFieldInfo.LastFuzzyKey.Load().(string) + if paramValue, ok = paramsMap[fuzzLastKey]; !ok { + paramKey, paramValue = fuzzyMatchingFieldName( + cachedFieldInfo.RemoveSymbolsFieldName, paramsMap, usedParamsKeyOrTagNameMap, + ) + ok = paramKey != "" + cachedFieldInfo.LastFuzzyKey.Store(paramKey) + } + if ok { + fieldValue = cachedFieldInfo.GetFieldReflectValueFrom(structValue) + if paramValue != nil { + if err = c.bindVarToStructField( + cachedFieldInfo, fieldValue, paramValue, option, + ); err != nil && !option.ContinueOnError { + return err + } + // handle same field name in nested struct. + if len(cachedFieldInfo.OtherSameNameField) > 0 { + if err = c.setOtherSameNameField( + cachedFieldInfo, paramValue, structValue, option, + ); err != nil && !option.ContinueOnError { + return err + } + } + } + usedParamsKeyOrTagNameMap[paramKey] = struct{}{} + } + } + return nil +} + +// fuzzy matching rule: +// to match field name and param key in case-insensitive and without symbols. +func fuzzyMatchingFieldName( + fieldName string, + paramsMap map[string]any, + usedParamsKeyMap map[string]struct{}, +) (string, any) { + for paramKey, paramValue := range paramsMap { + if _, ok := usedParamsKeyMap[paramKey]; ok { + continue + } + removeParamKeyUnderline := utils.RemoveSymbols(paramKey) + if strings.EqualFold(fieldName, removeParamKeyUnderline) { + return paramKey, paramValue + } + } + return "", nil +} + +// bindVarToStructField sets value to struct object attribute by name. +// each value to attribute converting comes into in this function. +func (c *Converter) bindVarToStructField( + cachedFieldInfo *structcache.CachedFieldInfo, + fieldValue reflect.Value, + srcValue any, + option StructOption, +) (err error) { + if !fieldValue.IsValid() { + return nil + } + // CanSet checks whether attribute is public accessible. + if !fieldValue.CanSet() { + return nil + } + defer func() { + if exception := recover(); exception != nil { + if err = c.bindVarToReflectValue(fieldValue, srcValue, option); err != nil { + err = gerror.Wrapf(err, `error binding srcValue to attribute "%s"`, cachedFieldInfo.FieldName()) + } + } + }() + // Directly converting. + if empty.IsNil(srcValue) { + fieldValue.Set(reflect.Zero(fieldValue.Type())) + return nil + } + // Try to call custom converter. + // Issue: https://github.com/gogf/gf/issues/3099 + var ( + customConverterInput reflect.Value + ok bool + ) + if cachedFieldInfo.HasCustomConvert { + if customConverterInput, ok = srcValue.(reflect.Value); !ok { + customConverterInput = reflect.ValueOf(srcValue) + } + if ok, err = c.callCustomConverter(customConverterInput, fieldValue); ok || err != nil { + return + } + } + if cachedFieldInfo.IsCommonInterface { + if ok, err = bindVarToReflectValueWithInterfaceCheck(fieldValue, srcValue); ok || err != nil { + return + } + } + // Common types use fast assignment logic + if cachedFieldInfo.ConvertFunc != nil { + return cachedFieldInfo.ConvertFunc(srcValue, fieldValue) + } + convertOption := ConvertOption{ + StructOption: option, + SliceOption: SliceOption{ContinueOnError: option.ContinueOnError}, + MapOption: MapOption{ContinueOnError: option.ContinueOnError}, + } + err = c.doConvertWithReflectValueSet( + fieldValue, doConvertInput{ + FromValue: srcValue, + ToTypeName: cachedFieldInfo.StructField.Type.String(), + ReferValue: fieldValue, + }, + convertOption, + ) + return err +} + +// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks. +func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value any) (bool, error) { + var pointer any + if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { + reflectValueAddr := reflectValue.Addr() + if reflectValueAddr.IsNil() || !reflectValueAddr.IsValid() { + 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 false, nil + } + pointer = reflectValue.Interface() + } + // UnmarshalValue. + if v, ok := pointer.(localinterface.IUnmarshalValue); ok { + return ok, v.UnmarshalValue(value) + } + // UnmarshalText. + if v, ok := pointer.(localinterface.IUnmarshalText); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } else if f, ok := value.(localinterface.IString); ok { + valueBytes = []byte(f.String()) + } + if len(valueBytes) > 0 { + return ok, v.UnmarshalText(valueBytes) + } + } + // UnmarshalJSON. + if v, ok := pointer.(localinterface.IUnmarshalJSON); ok { + var valueBytes []byte + if b, ok := value.([]byte); ok { + valueBytes = b + } else if s, ok := value.(string); ok { + valueBytes = []byte(s) + } else if f, ok := value.(localinterface.IString); ok { + valueBytes = []byte(f.String()) + } + + if len(valueBytes) > 0 { + // If it is not a valid JSON string, it then adds char `"` on its both sides to make it is. + if !json.Valid(valueBytes) { + newValueBytes := make([]byte, len(valueBytes)+2) + newValueBytes[0] = '"' + newValueBytes[len(newValueBytes)-1] = '"' + copy(newValueBytes[1:], valueBytes) + valueBytes = newValueBytes + } + return ok, v.UnmarshalJSON(valueBytes) + } + } + if v, ok := pointer.(localinterface.ISet); ok { + v.Set(value) + return ok, nil + } + return false, nil +} + +// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. +func (c *Converter) bindVarToReflectValue(structFieldValue reflect.Value, value any, option StructOption) (err error) { + // JSON content converting. + ok, err := c.doConvertWithJsonCheck(value, structFieldValue) + if err != nil { + return err + } + if ok { + return nil + } + + kind := structFieldValue.Kind() + // Converting using `Set` interface implements, for some types. + switch kind { + case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface: + if !structFieldValue.IsNil() { + if v, ok := structFieldValue.Interface().(localinterface.ISet); ok { + v.Set(value) + return nil + } + } + default: + } + + // Converting using reflection by kind. + switch kind { + case reflect.Map: + return c.MapToMap(value, structFieldValue, option.ParamKeyToAttrMap, MapOption{ + ContinueOnError: option.ContinueOnError, + }) + + case reflect.Struct: + // Recursively converting for struct attribute. + if err = c.Struct(value, structFieldValue, option); err != nil { + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + + // Note that the slice element might be type of struct, + // so it uses Struct function doing the converting internally. + case reflect.Slice, reflect.Array: + var ( + reflectArray reflect.Value + reflectValue = reflect.ValueOf(value) + convertOption = ConvertOption{ + StructOption: option, + SliceOption: SliceOption{ContinueOnError: option.ContinueOnError}, + MapOption: MapOption{ContinueOnError: option.ContinueOnError}, + } + ) + if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array { + reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len()) + if reflectValue.Len() > 0 { + var ( + elemType = reflectArray.Index(0).Type() + elemTypeName string + converted bool + ) + for i := 0; i < reflectValue.Len(); i++ { + converted = false + elemTypeName = elemType.Name() + if elemTypeName == "" { + elemTypeName = elemType.String() + } + var elem reflect.Value + if elemType.Kind() == reflect.Ptr { + elem = reflect.New(elemType.Elem()).Elem() + } else { + elem = reflect.New(elemType).Elem() + } + if elem.Kind() == reflect.Struct { + if err = c.Struct(reflectValue.Index(i).Interface(), elem, option); err == nil { + converted = true + } + } + if !converted { + err = c.doConvertWithReflectValueSet( + elem, doConvertInput{ + FromValue: reflectValue.Index(i).Interface(), + ToTypeName: elemTypeName, + ReferValue: elem, + }, + convertOption, + ) + if err != nil { + return err + } + } + if elemType.Kind() == reflect.Ptr { + // Before it sets the `elem` to array, do pointer converting if necessary. + elem = elem.Addr() + } + reflectArray.Index(i).Set(elem) + } + } + } else { + var ( + elem reflect.Value + elemType = structFieldValue.Type().Elem() + elemTypeName = elemType.Name() + converted bool + ) + switch reflectValue.Kind() { + case reflect.String: + // Value is empty string. + if reflectValue.IsZero() { + var elemKind = elemType.Kind() + // Try to find the original type kind of the slice element. + if elemKind == reflect.Ptr { + elemKind = elemType.Elem().Kind() + } + switch elemKind { + case reflect.String: + // Empty string cannot be assigned to string slice. + return nil + default: + } + } + default: + } + if elemTypeName == "" { + elemTypeName = elemType.String() + } + if elemType.Kind() == reflect.Ptr { + elem = reflect.New(elemType.Elem()).Elem() + } else { + elem = reflect.New(elemType).Elem() + } + if elem.Kind() == reflect.Struct { + if err = c.Struct(value, elem, option); err == nil { + converted = true + } + } + if !converted { + err = c.doConvertWithReflectValueSet( + elem, doConvertInput{ + FromValue: value, + ToTypeName: elemTypeName, + ReferValue: elem, + }, + convertOption, + ) + if err != nil { + return err + } + } + if elemType.Kind() == reflect.Ptr { + // Before it sets the `elem` to array, do pointer converting if necessary. + elem = elem.Addr() + } + reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1) + reflectArray.Index(0).Set(elem) + } + structFieldValue.Set(reflectArray) + + case reflect.Ptr: + if structFieldValue.IsNil() || structFieldValue.IsZero() { + // Nil or empty pointer, it creates a new one. + item := reflect.New(structFieldValue.Type().Elem()) + if ok, err = bindVarToReflectValueWithInterfaceCheck(item, value); ok { + structFieldValue.Set(item) + return err + } + elem := item.Elem() + if err = c.bindVarToReflectValue(elem, value, option); err == nil { + structFieldValue.Set(elem.Addr()) + } + } else { + // Not empty pointer, it assigns values to it. + return c.bindVarToReflectValue(structFieldValue.Elem(), value, option) + } + + // It mainly and specially handles the interface of nil value. + case reflect.Interface: + if value == nil { + // Specially. + structFieldValue.Set(reflect.ValueOf((*any)(nil))) + } else { + // Note there's reflect conversion mechanism here. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + + default: + defer func() { + if exception := recover(); exception != nil { + err = gerror.NewCodef( + gcode.CodeInternalPanic, + `cannot convert value "%+v" to type "%s":%+v`, + value, + structFieldValue.Type().String(), + exception, + ) + } + }() + // It here uses reflect converting `value` to type of the attribute and assigns + // the result value to the attribute. It might fail and panic if the usual Go + // conversion rules do not allow conversion. + structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) + } + return nil +} diff --git a/util/gconv/internal/converter/converter_structs.go b/util/gconv/internal/converter/converter_structs.go new file mode 100644 index 00000000000..d2eeec733fb --- /dev/null +++ b/util/gconv/internal/converter/converter_structs.go @@ -0,0 +1,120 @@ +// 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 converter + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" +) + +// Structs converts any slice to given struct slice. +// +// It automatically checks and converts json string to []map if `params` is string/[]byte. +// +// The parameter `pointer` should be type of pointer to slice of struct. +// Note that if `pointer` is a pointer to another pointer of type of slice of struct, +// it will create the struct/pointer internally. +func (c *Converter) Structs( + params any, pointer any, sliceOption SliceOption, structOption StructOption, +) (err error) { + defer func() { + // Catch the panic, especially the reflection operation panics. + if exception := recover(); exception != nil { + if v, ok := exception.(error); ok && gerror.HasStack(v) { + err = v + } else { + err = gerror.NewCodeSkipf(gcode.CodeInternalPanic, 1, "%+v", exception) + } + } + }() + + // 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, + ) + } + } + // Converting `params` to map slice. + var ( + paramsList []any + paramsRv = reflect.ValueOf(params) + paramsKind = paramsRv.Kind() + ) + for paramsKind == reflect.Ptr { + paramsRv = paramsRv.Elem() + paramsKind = paramsRv.Kind() + } + switch paramsKind { + case reflect.Slice, reflect.Array: + paramsList = make([]any, paramsRv.Len()) + for i := 0; i < paramsRv.Len(); i++ { + paramsList[i] = paramsRv.Index(i).Interface() + } + default: + paramsMaps, err := c.SliceMap(params, sliceOption, MapOption{ + ContinueOnError: structOption.ContinueOnError, + }) + if err != nil { + return err + } + paramsList = make([]any, len(paramsMaps)) + for i := 0; i < len(paramsMaps); i++ { + paramsList[i] = paramsMaps[i] + } + } + // If `params` is an empty slice, no conversion. + if len(paramsList) == 0 { + return nil + } + var ( + reflectElemArray = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsList), len(paramsList)) + itemType = reflectElemArray.Index(0).Type() + itemTypeKind = itemType.Kind() + pointerRvElem = pointerRv.Elem() + pointerRvLength = pointerRvElem.Len() + ) + if itemTypeKind == reflect.Ptr { + // Pointer element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + // Might be nil. + tempReflectValue = pointerRvElem.Index(i).Elem() + } + if !tempReflectValue.IsValid() { + tempReflectValue = reflect.New(itemType.Elem()).Elem() + } + if err = c.Struct(paramsList[i], tempReflectValue, structOption); err != nil { + return err + } + reflectElemArray.Index(i).Set(tempReflectValue.Addr()) + } + } else { + // Struct element. + for i := 0; i < len(paramsList); i++ { + var tempReflectValue reflect.Value + if i < pointerRvLength { + tempReflectValue = pointerRvElem.Index(i) + } else { + tempReflectValue = reflect.New(itemType).Elem() + } + if err = c.Struct(paramsList[i], tempReflectValue, structOption); err != nil { + return err + } + reflectElemArray.Index(i).Set(tempReflectValue) + } + } + pointerRv.Elem().Set(reflectElemArray) + return nil +} diff --git a/util/gconv/internal/converter/converter_time.go b/util/gconv/internal/converter/converter_time.go new file mode 100644 index 00000000000..05eae209faa --- /dev/null +++ b/util/gconv/internal/converter/converter_time.go @@ -0,0 +1,111 @@ +// 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 converter + +import ( + "time" + + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Time converts `any` to time.Time. +func (c *Converter) Time(any interface{}, format ...string) (time.Time, error) { + // It's already this type. + if len(format) == 0 { + if v, ok := any.(time.Time); ok { + return v, nil + } + } + t, err := c.GTime(any, format...) + if err != nil { + return time.Time{}, err + } + if t != nil { + return t.Time, nil + } + return time.Time{}, nil +} + +// Duration converts `any` to time.Duration. +// If `any` is string, then it uses time.ParseDuration to convert it. +// If `any` is numeric, then it converts `any` as nanoseconds. +func (c *Converter) Duration(any interface{}) (time.Duration, error) { + // It's already this type. + if v, ok := any.(time.Duration); ok { + return v, nil + } + s, err := c.String(any) + if err != nil { + return 0, err + } + if !utils.IsNumeric(s) { + return gtime.ParseDuration(s) + } + i, err := c.Int64(any) + if err != nil { + return 0, err + } + return time.Duration(i), nil +} + +// GTime converts `any` to *gtime.Time. +// The parameter `format` can be used to specify the format of `any`. +// It returns the converted value that matched the first format of the formats slice. +// If no `format` given, it converts `any` using gtime.NewFromTimeStamp if `any` is numeric, +// or using gtime.StrToTime if `any` is string. +func (c *Converter) GTime(any interface{}, format ...string) (*gtime.Time, error) { + if empty.IsNil(any) { + return nil, nil + } + if v, ok := any.(localinterface.IGTime); ok { + return v.GTime(format...), nil + } + // It's already this type. + if len(format) == 0 { + if v, ok := any.(*gtime.Time); ok { + return v, nil + } + if t, ok := any.(time.Time); ok { + return gtime.New(t), nil + } + if t, ok := any.(*time.Time); ok { + return gtime.New(t), nil + } + } + s, err := c.String(any) + if err != nil { + return nil, err + } + if len(s) == 0 { + return gtime.New(), nil + } + // Priority conversion using given format. + if len(format) > 0 { + for _, item := range format { + t, err := gtime.StrToTimeFormat(s, item) + if err != nil { + return nil, err + } + if t != nil { + return t, nil + } + } + return nil, nil + } + if utils.IsNumeric(s) { + i, err := c.Int64(s) + if err != nil { + return nil, err + } + return gtime.NewFromTimeStamp(i), nil + } else { + return gtime.StrToTime(s) + } +} diff --git a/util/gconv/internal/converter/converter_uint.go b/util/gconv/internal/converter/converter_uint.go new file mode 100644 index 00000000000..2055b0c0cb2 --- /dev/null +++ b/util/gconv/internal/converter/converter_uint.go @@ -0,0 +1,161 @@ +// 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 converter + +import ( + "math" + "reflect" + "strconv" + + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/util/gconv/internal/localinterface" +) + +// Uint converts `any` to uint. +func (c *Converter) Uint(any any) (uint, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint(v), err +} + +// Uint8 converts `any` to uint8. +func (c *Converter) Uint8(any any) (uint8, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint8); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint8(v), err +} + +// Uint16 converts `any` to uint16. +func (c *Converter) Uint16(any any) (uint16, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint16); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint16(v), err +} + +// Uint32 converts `any` to uint32. +func (c *Converter) Uint32(any any) (uint32, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint32); ok { + return v, nil + } + v, err := c.Uint64(any) + return uint32(v), err +} + +// Uint64 converts `any` to uint64. +func (c *Converter) Uint64(any any) (uint64, error) { + if empty.IsNil(any) { + return 0, nil + } + if v, ok := any.(uint64); ok { + return v, nil + } + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + val := rv.Int() + if val < 0 { + return uint64(val), gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot convert negative value "%d" to uint64`, + val, + ) + } + return uint64(val), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint(), nil + case reflect.Uintptr: + return rv.Uint(), nil + case reflect.Float32, reflect.Float64: + val := rv.Float() + if val < 0 { + return uint64(val), gerror.NewCodef( + gcode.CodeInvalidParameter, + `cannot convert negative value "%f" to uint64`, + val, + ) + } + return uint64(val), nil + case reflect.Bool: + if rv.Bool() { + return 1, nil + } + return 0, nil + case reflect.Ptr: + if rv.IsNil() { + return 0, nil + } + if f, ok := any.(localinterface.IUint64); ok { + return f.Uint64(), nil + } + return c.Uint64(rv.Elem().Interface()) + case reflect.Slice: + if rv.Type().Elem().Kind() == reflect.Uint8 { + return gbinary.DecodeToUint64(rv.Bytes()), nil + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport slice type "%s" for converting to uint64`, + rv.Type().String(), + ) + case reflect.String: + var s = rv.String() + // Hexadecimal + if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { + v, err := strconv.ParseUint(s[2:], 16, 64) + if err == nil { + return v, nil + } + return 0, gerror.WrapCodef( + gcode.CodeInvalidParameter, + err, + `cannot convert hexadecimal string "%s" to uint64`, + s, + ) + } + // Decimal + if v, err := strconv.ParseUint(s, 10, 64); err == nil { + return v, nil + } + // Float64 + if v, err := c.Float64(any); err == nil { + if math.IsNaN(v) { + return 0, nil + } + return uint64(v), nil + } + default: + if f, ok := any.(localinterface.IUint64); ok { + return f.Uint64(), nil + } + } + return 0, gerror.NewCodef( + gcode.CodeInvalidParameter, + `unsupport value type "%s" for converting to uint64`, + reflect.TypeOf(any).String(), + ) +} diff --git a/util/gconv/internal/localinterface/localinterface.go b/util/gconv/internal/localinterface/localinterface.go index 63ef7d0789d..d6821b2c48f 100644 --- a/util/gconv/internal/localinterface/localinterface.go +++ b/util/gconv/internal/localinterface/localinterface.go @@ -9,9 +9,9 @@ package localinterface import "github.com/gogf/gf/v2/os/gtime" -// IVal is used for type assert api for String(). +// IVal is used for type assert api for Val(). type IVal interface { - Val() interface{} + Val() any } // IString is used for type assert api for String(). @@ -56,12 +56,12 @@ type IBytes interface { // IInterface is used for type assert api for Interface(). type IInterface interface { - Interface() interface{} + Interface() any } // IInterfaces is used for type assert api for Interfaces(). type IInterfaces interface { - Interfaces() []interface{} + Interfaces() []any } // IFloats is used for type assert api for Floats(). @@ -86,7 +86,7 @@ type IUints interface { // IMapStrAny is the interface support for converting struct parameter to map. type IMapStrAny interface { - MapStrAny() map[string]interface{} + MapStrAny() map[string]any } // IUnmarshalText is the interface for custom defined types customizing value assignment. @@ -104,12 +104,12 @@ type IUnmarshalJSON interface { // IUnmarshalValue is the interface for custom defined types customizing value assignment. // Note that only pointer can implement interface IUnmarshalValue. type IUnmarshalValue interface { - UnmarshalValue(interface{}) error + UnmarshalValue(any) error } // ISet is the interface for custom value assignment. type ISet interface { - Set(value interface{}) (old interface{}) + Set(value any) (old any) } // IGTime is the interface for gtime.Time converting. diff --git a/util/gconv/internal/structcache/structcache.go b/util/gconv/internal/structcache/structcache.go index cdb97388dad..044f8a93526 100644 --- a/util/gconv/internal/structcache/structcache.go +++ b/util/gconv/internal/structcache/structcache.go @@ -9,24 +9,80 @@ package structcache import ( "reflect" + "sync" "github.com/gogf/gf/v2/util/gconv/internal/localinterface" ) -var ( - // customConvertTypeMap is used to store whether field types are registered to custom conversions - // For example: - // func (src *TypeA) (dst *TypeB,err error) - // This map will store `TypeB` for quick judgment during assignment. - customConvertTypeMap = map[reflect.Type]struct{}{} -) +type interfaceTypeConverter struct { + interfaceType reflect.Type + convertFunc AnyConvertFunc +} + +// Converter is the configuration for type converting. +type Converter struct { + // map[reflect.Type]*CachedStructInfo + cachedStructsInfoMap sync.Map + + // anyToTypeConvertMap for custom type converting from any to its reflect.Value. + anyToTypeConvertMap map[reflect.Type]AnyConvertFunc + + // interfaceToTypeConvertMap used for converting any interface type + // the reason why map is not used here, is because interface types cannot be instantiated + interfaceToTypeConvertMap []interfaceTypeConverter + + // typeConverterFuncMarkMap is used to store whether field types are registered to custom conversions + typeConverterFuncMarkMap map[reflect.Type]struct{} +} + +// AnyConvertFunc is the function type for converting any to specified type. +type AnyConvertFunc func(from any, to reflect.Value) error + +// NewConverter creates and returns a new Converter object. +func NewConverter() *Converter { + return &Converter{ + cachedStructsInfoMap: sync.Map{}, + typeConverterFuncMarkMap: make(map[reflect.Type]struct{}), + anyToTypeConvertMap: make(map[reflect.Type]AnyConvertFunc), + } +} -// RegisterCustomConvertType registers custom -func RegisterCustomConvertType(fieldType reflect.Type) { +// MarkTypeConvertFunc marks converting function registered for custom type. +func (cf *Converter) MarkTypeConvertFunc(fieldType reflect.Type) { if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } - customConvertTypeMap[fieldType] = struct{}{} + cf.typeConverterFuncMarkMap[fieldType] = struct{}{} +} + +// RegisterAnyConvertFunc registers custom type converting function for specified type. +func (cf *Converter) RegisterAnyConvertFunc(t reflect.Type, convertFunc AnyConvertFunc) { + if t == nil || convertFunc == nil { + return + } + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.Interface { + cf.interfaceToTypeConvertMap = append(cf.interfaceToTypeConvertMap, interfaceTypeConverter{ + interfaceType: t, + convertFunc: convertFunc, + }) + return + } + cf.anyToTypeConvertMap[t] = convertFunc +} + +func (cf *Converter) checkTypeImplInterface(t reflect.Type) AnyConvertFunc { + if t.Kind() != reflect.Ptr { + t = reflect.PointerTo(t) + } + for _, inter := range cf.interfaceToTypeConvertMap { + if t.Implements(inter.interfaceType) { + return inter.convertFunc + } + } + return nil } var ( diff --git a/util/gconv/internal/structcache/structcache_cached.go b/util/gconv/internal/structcache/structcache_cached.go index 1c7d4cf322b..041b3c79be6 100644 --- a/util/gconv/internal/structcache/structcache_cached.go +++ b/util/gconv/internal/structcache/structcache_cached.go @@ -8,48 +8,19 @@ package structcache import ( "reflect" - "sync" - "time" "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gtag" ) -// CommonConverter holds some converting functions of common types for internal usage. -type CommonConverter struct { - Int64 func(any interface{}) int64 - Uint64 func(any interface{}) uint64 - String func(any interface{}) string - Float32 func(any interface{}) float32 - Float64 func(any interface{}) float64 - Time func(any interface{}, format ...string) time.Time - GTime func(any interface{}, format ...string) *gtime.Time - Bytes func(any interface{}) []byte - Bool func(any interface{}) bool -} - -var ( - // map[reflect.Type]*CachedStructInfo - cachedStructsInfoMap = sync.Map{} - - // localCommonConverter holds some converting functions of common types for internal usage. - localCommonConverter CommonConverter -) - -// RegisterCommonConverter registers the CommonConverter for local usage. -func RegisterCommonConverter(commonConverter CommonConverter) { - localCommonConverter = commonConverter -} - // GetCachedStructInfo retrieves or parses and returns a cached info for certain struct type. // The given `structType` should be type of struct. -func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStructInfo { +func (cf *Converter) GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStructInfo { if structType.Kind() != reflect.Struct { return nil } // check if it has been cached. - cachedStructInfo, ok := getCachedConvertStructInfo(structType) + cachedStructInfo, ok := cf.getCachedConvertStructInfo(structType) if ok { // directly returns the cached struct info if already exists. return cachedStructInfo @@ -58,9 +29,7 @@ func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStr // else create one. // it parses and generates a cache info for given struct type. - cachedStructInfo = &CachedStructInfo{ - tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo), - } + cachedStructInfo = NewCachedStructInfo(cf) var ( priorityTagArray []string parentIndex = make([]int, 0) @@ -70,19 +39,19 @@ func GetCachedStructInfo(structType reflect.Type, priorityTag string) *CachedStr } else { priorityTagArray = gtag.StructTagPriority } - parseStructToCachedStructInfo(structType, parentIndex, cachedStructInfo, priorityTagArray) - storeCachedStructInfo(structType, cachedStructInfo) + cf.parseStructToCachedStructInfo(structType, parentIndex, cachedStructInfo, priorityTagArray) + cf.storeCachedStructInfo(structType, cachedStructInfo) return cachedStructInfo } -func storeCachedStructInfo(structType reflect.Type, cachedStructInfo *CachedStructInfo) { +func (cf *Converter) storeCachedStructInfo(structType reflect.Type, cachedStructInfo *CachedStructInfo) { // Temporarily enabled as an experimental feature - cachedStructsInfoMap.Store(structType, cachedStructInfo) + cf.cachedStructsInfoMap.Store(structType, cachedStructInfo) } -func getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, bool) { +func (cf *Converter) getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, bool) { // Temporarily enabled as an experimental feature - v, ok := cachedStructsInfoMap.Load(structType) + v, ok := cf.cachedStructsInfoMap.Load(structType) if ok { return v.(*CachedStructInfo), ok } @@ -91,7 +60,7 @@ func getCachedConvertStructInfo(structType reflect.Type) (*CachedStructInfo, boo // parseStructToCachedStructInfo parses given struct reflection type and stores its fields info into given CachedStructInfo. // It stores nothing into CachedStructInfo if given struct reflection type has no fields. -func parseStructToCachedStructInfo( +func (cf *Converter) parseStructToCachedStructInfo( structType reflect.Type, fieldIndexes []int, cachedStructInfo *CachedStructInfo, @@ -136,7 +105,7 @@ func parseStructToCachedStructInfo( // Do not add anonymous structures without tags cachedStructInfo.AddField(structField, append(copyFieldIndexes, i), priorityTagArray) } - parseStructToCachedStructInfo(fieldType, append(copyFieldIndexes, i), cachedStructInfo, priorityTagArray) + cf.parseStructToCachedStructInfo(fieldType, append(copyFieldIndexes, i), cachedStructInfo, priorityTagArray) continue } // Do not directly use append(fieldIndexes, i) diff --git a/util/gconv/internal/structcache/structcache_cached_field_info.go b/util/gconv/internal/structcache/structcache_cached_field_info.go index 6b07cae15cc..084b609bb45 100644 --- a/util/gconv/internal/structcache/structcache_cached_field_info.go +++ b/util/gconv/internal/structcache/structcache_cached_field_info.go @@ -51,6 +51,8 @@ type CachedFieldInfoBase struct { IsCommonInterface bool // HasCustomConvert marks there custom converting function for this field type. + // A custom converting function is a function that user defined for converting specified type + // to another type. HasCustomConvert bool // StructField is the type info of this field. @@ -74,7 +76,7 @@ type CachedFieldInfoBase struct { OtherSameNameField []*CachedFieldInfo // ConvertFunc is the converting function for this field. - ConvertFunc func(from any, to reflect.Value) + ConvertFunc AnyConvertFunc // The last fuzzy matching key for this field. // The fuzzy matching occurs only if there are no direct tag and field name matching in the params map. diff --git a/util/gconv/internal/structcache/structcache_cached_struct_info.go b/util/gconv/internal/structcache/structcache_cached_struct_info.go index bf6a10aad91..f3ff52d5d54 100644 --- a/util/gconv/internal/structcache/structcache_cached_struct_info.go +++ b/util/gconv/internal/structcache/structcache_cached_struct_info.go @@ -9,14 +9,17 @@ package structcache import ( "reflect" "strings" - "time" "github.com/gogf/gf/v2/internal/utils" - "github.com/gogf/gf/v2/os/gtime" ) // CachedStructInfo holds the cached info for certain struct. type CachedStructInfo struct { + // All sub attributes field info slice. + fieldConvertInfos []*CachedFieldInfo + + converter *Converter + // This map field is mainly used in the bindStructWithLoopParamsMap method // key = field's name // Will save all field names and PriorityTagAndFieldName @@ -25,9 +28,19 @@ type CachedStructInfo struct { // // It will be stored twice, which keys are `name` and `field`. tagOrFiledNameToFieldInfoMap map[string]*CachedFieldInfo +} - // All sub attributes field info slice. - FieldConvertInfos []*CachedFieldInfo +// NewCachedStructInfo creates and returns a new CachedStructInfo object. +func NewCachedStructInfo(converter *Converter) *CachedStructInfo { + return &CachedStructInfo{ + tagOrFiledNameToFieldInfoMap: make(map[string]*CachedFieldInfo), + fieldConvertInfos: make([]*CachedFieldInfo, 0), + converter: converter, + } +} + +func (csi *CachedStructInfo) GetFieldConvertInfos() []*CachedFieldInfo { + return csi.fieldConvertInfos } func (csi *CachedStructInfo) HasNoFields() bool { @@ -46,7 +59,7 @@ func (csi *CachedStructInfo) AddField(field reflect.StructField, fieldIndexes [] field, fieldIndexes, priorityTags, cachedFieldInfo, tagOrFieldName, ) if newFieldInfo.IsField { - csi.FieldConvertInfos = append(csi.FieldConvertInfos, newFieldInfo) + csi.fieldConvertInfos = append(csi.fieldConvertInfos, newFieldInfo) } // if the field info by `tagOrFieldName` already cached, // it so adds this new field info to other same name field. @@ -82,7 +95,9 @@ func (csi *CachedStructInfo) makeOrCopyCachedInfo( // copyCachedInfoWithFieldIndexes copies and returns a new CachedFieldInfo based on given CachedFieldInfo, but different // FieldIndexes. Mainly used for copying fields with the same name and type. -func (csi *CachedStructInfo) copyCachedInfoWithFieldIndexes(cfi *CachedFieldInfo, fieldIndexes []int) *CachedFieldInfo { +func (csi *CachedStructInfo) copyCachedInfoWithFieldIndexes( + cfi *CachedFieldInfo, fieldIndexes []int, +) *CachedFieldInfo { base := CachedFieldInfoBase{} base = *cfi.CachedFieldInfoBase base.FieldIndexes = fieldIndexes @@ -98,7 +113,7 @@ func (csi *CachedStructInfo) makeCachedFieldInfo( IsCommonInterface: checkTypeIsCommonInterface(field), StructField: field, FieldIndexes: fieldIndexes, - ConvertFunc: csi.genFieldConvertFunc(field.Type.String()), + ConvertFunc: csi.genFieldConvertFunc(field.Type), HasCustomConvert: csi.checkTypeHasCustomConvert(field.Type), PriorityTagAndFieldName: csi.genPriorityTagAndFieldName(field, priorityTags), RemoveSymbolsFieldName: utils.RemoveSymbols(field.Name), @@ -109,58 +124,27 @@ func (csi *CachedStructInfo) makeCachedFieldInfo( } } -func (csi *CachedStructInfo) genFieldConvertFunc(fieldType string) (convertFunc func(from any, to reflect.Value)) { - if fieldType[0] == '*' { - convertFunc = csi.genFieldConvertFunc(fieldType[1:]) - if convertFunc == nil { - return nil - } - return csi.genPtrConvertFunc(convertFunc) +func (csi *CachedStructInfo) genFieldConvertFunc(fieldType reflect.Type) (convertFunc AnyConvertFunc) { + ptr := 0 + for fieldType.Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + ptr++ } - switch fieldType { - case "int", "int8", "int16", "int32", "int64": - convertFunc = func(from any, to reflect.Value) { - to.SetInt(localCommonConverter.Int64(from)) - } - case "uint", "uint8", "uint16", "uint32", "uint64": - convertFunc = func(from any, to reflect.Value) { - to.SetUint(localCommonConverter.Uint64(from)) - } - case "string": - convertFunc = func(from any, to reflect.Value) { - to.SetString(localCommonConverter.String(from)) - } - case "float32": - convertFunc = func(from any, to reflect.Value) { - to.SetFloat(float64(localCommonConverter.Float32(from))) - } - case "float64": - convertFunc = func(from any, to reflect.Value) { - to.SetFloat(localCommonConverter.Float64(from)) - } - case "Time", "time.Time": - convertFunc = func(from any, to reflect.Value) { - *to.Addr().Interface().(*time.Time) = localCommonConverter.Time(from) - } - case "GTime", "gtime.Time": - convertFunc = func(from any, to reflect.Value) { - v := localCommonConverter.GTime(from) - if v == nil { - v = gtime.New() - } - *to.Addr().Interface().(*gtime.Time) = *v - } - case "bool": - convertFunc = func(from any, to reflect.Value) { - to.SetBool(localCommonConverter.Bool(from)) - } - case "[]byte": - convertFunc = func(from any, to reflect.Value) { - to.SetBytes(localCommonConverter.Bytes(from)) - } - default: + convertFunc = csi.converter.anyToTypeConvertMap[fieldType] + if convertFunc == nil { + // If the registered custom implementation cannot be found, + // try to check if there is an implementation interface + convertFunc = csi.converter.checkTypeImplInterface(fieldType) + } + // if the registered type is not found and + // the corresponding interface is not implemented, return directly + if convertFunc == nil { return nil } + for i := 0; i < ptr; i++ { + // If it is a pointer type, it needs to be packaged + convertFunc = genPtrConvertFunc(convertFunc) + } return convertFunc } @@ -188,21 +172,19 @@ func (csi *CachedStructInfo) genPriorityTagAndFieldName( return } +func genPtrConvertFunc(convertFunc AnyConvertFunc) AnyConvertFunc { + return func(from any, to reflect.Value) error { + if to.IsNil() { + to.Set(reflect.New(to.Type().Elem())) + } + return convertFunc(from, to.Elem()) + } +} + func (csi *CachedStructInfo) checkTypeHasCustomConvert(fieldType reflect.Type) bool { if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } - _, ok := customConvertTypeMap[fieldType] + _, ok := csi.converter.typeConverterFuncMarkMap[fieldType] return ok } - -func (csi *CachedStructInfo) genPtrConvertFunc( - convertFunc func(from any, to reflect.Value), -) func(from any, to reflect.Value) { - return func(from any, to reflect.Value) { - if to.IsNil() { - to.Set(reflect.New(to.Type().Elem())) - } - convertFunc(from, to.Elem()) - } -}