From 68e760d13add02642e7ef538fc34118d5066cfe0 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 15 Sep 2021 21:17:45 +0800 Subject: [PATCH] fix issue @1380 --- database/gdb/gdb_type_record.go | 5 - database/gdb/gdb_type_result.go | 7 +- database/gdb/gdb_z_mysql_struct_test.go | 117 +++++++++++++ database/gdb/testdata/issue1380.sql | 35 ++++ os/gcron/gcron_schedule.go | 4 +- test/gtest/gtest_util.go | 31 +++- util/gconv/gconv.go | 215 +++++++++++++----------- util/gconv/gconv_interface.go | 5 + util/gconv/gconv_maptomap.go | 2 +- util/gconv/gconv_maptomaps.go | 2 +- util/gconv/gconv_slice_float.go | 16 ++ util/gconv/gconv_slice_int.go | 45 +++-- util/gconv/gconv_slice_str.go | 12 +- util/gconv/gconv_slice_uint.go | 24 +++ util/gconv/gconv_struct.go | 114 ++++++++----- util/gconv/gconv_structs.go | 2 +- util/gconv/gconv_z_unit_scan_test.go | 82 +++++++++ 17 files changed, 542 insertions(+), 176 deletions(-) create mode 100644 database/gdb/testdata/issue1380.sql diff --git a/database/gdb/gdb_type_record.go b/database/gdb/gdb_type_record.go index 14c26b9e6..e5c471892 100644 --- a/database/gdb/gdb_type_record.go +++ b/database/gdb/gdb_type_record.go @@ -14,11 +14,6 @@ import ( "github.com/gogf/gf/util/gconv" ) -// Interface converts and returns `r` as type of interface{}. -func (r Record) Interface() interface{} { - return r -} - // Json converts `r` to JSON format content. func (r Record) Json() string { content, _ := gparser.VarToJson(r.Map()) diff --git a/database/gdb/gdb_type_result.go b/database/gdb/gdb_type_result.go index cfe1ba92a..619cd33c5 100644 --- a/database/gdb/gdb_type_result.go +++ b/database/gdb/gdb_type_result.go @@ -13,11 +13,6 @@ import ( "math" ) -// Interface converts and returns `r` as type of interface{}. -func (r Result) Interface() interface{} { - return r -} - // IsEmpty checks and returns whether `r` is empty. func (r Result) IsEmpty() bool { return r.Len() == 0 @@ -33,7 +28,7 @@ func (r Result) Size() int { return r.Len() } -// Chunk splits an Result into multiple Results, +// Chunk splits a Result into multiple Results, // the size of each array is determined by `size`. // The last chunk may contain less than size elements. func (r Result) Chunk(size int) []Result { diff --git a/database/gdb/gdb_z_mysql_struct_test.go b/database/gdb/gdb_z_mysql_struct_test.go index 5f12869a2..1ec74b655 100644 --- a/database/gdb/gdb_z_mysql_struct_test.go +++ b/database/gdb/gdb_z_mysql_struct_test.go @@ -14,6 +14,7 @@ import ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/text/gstr" "github.com/gogf/gf/util/gconv" "reflect" "testing" @@ -480,3 +481,119 @@ 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 + } + + var ( + table = "jfy_gift" + ) + array := gstr.SplitAndTrim(gtest.TestDataContent(`issue1380.sql`), ";") + for _, v := range array { + if _, err := db.Exec(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/database/gdb/testdata/issue1380.sql b/database/gdb/testdata/issue1380.sql new file mode 100644 index 000000000..59aefb551 --- /dev/null +++ b/database/gdb/testdata/issue1380.sql @@ -0,0 +1,35 @@ +CREATE TABLE `jfy_gift` ( +`id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT, +`gift_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品名称', +`at_least_recharge_count` int(0) UNSIGNED NOT NULL DEFAULT 1 COMMENT '最少兑换数量', +`comments` json NOT NULL COMMENT '礼品留言', +`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '礼品详情', +`cost_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '成本价', +`cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '封面', +`covers` json NOT NULL COMMENT '礼品图片库', +`description` varchar(62) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '礼品备注', +`express_type` json NOT NULL COMMENT '配送方式', +`gift_type` int(0) NOT NULL COMMENT '礼品类型:1:实物;2:虚拟;3:优惠券;4:积分券', +`has_props` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否有多个属性', +`is_limit_sell` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否限购', +`limit_customer_tags` json NOT NULL COMMENT '语序购买的会员标签', +`limit_sell_custom` tinyint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启允许购买的会员标签', +`limit_sell_cycle` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '限购周期', +`limit_sell_cycle_count` int(0) NOT NULL COMMENT '限购期内允许购买的数量', +`limit_sell_type` tinyint(0) NOT NULL COMMENT '限购类型', +`market_price` decimal(10, 2) NOT NULL COMMENT '市场价', +`out_sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内部编码', +`props` json NOT NULL COMMENT '规格', +`skus` json NOT NULL COMMENT 'SKU', +`score_price` decimal(10, 2) NOT NULL COMMENT '兑换所需积分', +`stock` int(0) NOT NULL COMMENT '库存', +`create_at` datetime(0) NOT NULL COMMENT '创建日期', +`store_id` int(0) NOT NULL COMMENT '所属商城', +`status` int(0) UNSIGNED NULL DEFAULT 1 COMMENT '1:下架;20:审核中;30:复审中;99:上架', +`view_count` int(0) NOT NULL DEFAULT 0 COMMENT '访问量', +`sell_count` int(0) NULL DEFAULT 0 COMMENT '销量', +PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + + +INSERT INTO `jfy_gift` VALUES (17, 'GIFT', 1, '[{\"name\": \"身份证\", \"field\": \"idcard\", \"required\": false}, {\"name\": \"留言2\", \"field\": \"text\", \"required\": false}]', '

礼品详情

', 0.00, '', '{\"list\": [{\"uid\": \"vc-upload-1629292486099-3\", \"url\": \"https://cdn.taobao.com/sULsYiwaOPjsKGoBXwKtuewPzACpBDfQ.jpg\", \"name\": \"O1CN01OH6PIP1Oc5ot06U17_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-4\", \"url\": \"https://cdn.taobao.com/lqLHDcrFTgNvlWyXfLYZwmsrODzIBtFH.jpg\", \"name\": \"O1CN018hBckI1Oc5ouc8ppl_!!922361725.jpg\", \"status\": \"done\"}, {\"uid\": \"vc-upload-1629292486099-5\", \"url\": \"https://cdn.taobao.com/pvqyutXckICmHhbPBQtrVLHuMlXuGxUg.jpg\", \"name\": \"O1CN0185Ubp91Oc5osQTTcc_!!922361725.jpg\", \"status\": \"done\"}]}', '支持个性定制的父亲节老师长辈的专属礼物', '[\"快递包邮\", \"同城配送\"]', 1, 0, 0, '[]', 0, 'day', 0, 1, 0.00, '259402', '[{\"name\": \"颜色\", \"values\": [\"红色\", \"蓝色\"]}]', '[{\"name\": \"red\", \"stock\": 10, \"gift_id\": 1, \"cost_price\": 80, \"score_price\": 188, \"market_price\": 388}, {\"name\": \"blue\", \"stock\": 100, \"gift_id\": 2, \"cost_price\": 81, \"score_price\": 200, \"market_price\": 288}]', 10.00, 0, '2021-08-18 21:26:13', 100004, 99, 0, 0); diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index 8bfb68a35..8291394c4 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -219,11 +219,11 @@ func parsePatternItemValue(value string, itemType int) (int, error) { // it converts the value to number according to predefined map. switch itemType { case patternItemTypeWeek: - if number, ok := monthMap[strings.ToLower(value)]; ok { + if number, ok := weekMap[strings.ToLower(value)]; ok { return number, nil } case patternItemTypeMonth: - if number, ok := weekMap[strings.ToLower(value)]; ok { + if number, ok := monthMap[strings.ToLower(value)]; ok { return number, nil } } diff --git a/test/gtest/gtest_util.go b/test/gtest/gtest_util.go index a1644afa2..2b525b647 100644 --- a/test/gtest/gtest_util.go +++ b/test/gtest/gtest_util.go @@ -9,7 +9,9 @@ package gtest import ( "fmt" "github.com/gogf/gf/internal/empty" + "io/ioutil" "os" + "path/filepath" "reflect" "testing" @@ -22,7 +24,7 @@ const ( pathFilterKey = "/test/gtest/gtest" ) -// C creates an unit testing case. +// C creates a unit testing case. // The parameter `t` is the pointer to testing.T of stdlib (*testing.T). // The parameter `f` is the closure function for unit testing case. func C(t *testing.T, f func(t *T)) { @@ -35,7 +37,7 @@ func C(t *testing.T, f func(t *T)) { f(&T{t}) } -// Case creates an unit testing case. +// Case creates a unit testing case. // The parameter `t` is the pointer to testing.T of stdlib (*testing.T). // The parameter `f` is the closure function for unit testing case. // Deprecated. @@ -357,3 +359,28 @@ func AssertNil(value interface{}) { } AssertNE(value, nil) } + +// TestDataPath retrieves and returns the testdata path of current package, +// which is used for unit testing cases only. +// The optional parameter `names` specifies the sub-folders/sub-files, +// which will be joined with current system separator and returned with the path. +func TestDataPath(names ...string) string { + _, path, _ := gdebug.CallerWithFilter(pathFilterKey) + path = filepath.Dir(path) + string(filepath.Separator) + "testdata" + for _, name := range names { + path += string(filepath.Separator) + name + } + return path +} + +// TestDataContent retrieves and returns the file content for specified testdata path of current package +func TestDataContent(names ...string) string { + path := TestDataPath(names...) + if path != "" { + data, err := ioutil.ReadFile(path) + if err == nil { + return string(data) + } + } + return "" +} diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 0a72171f8..2c4af34cb 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -53,235 +53,235 @@ type doConvertInput struct { Extra []interface{} // Extra values for implementing the converting. } -// doConvert does commonly used types converting. -func doConvert(input doConvertInput) interface{} { - switch input.ToTypeName { +// doConvert does commonly use types converting. +func doConvert(in doConvertInput) interface{} { + switch in.ToTypeName { case "int": - return Int(input.FromValue) + return Int(in.FromValue) case "*int": - if _, ok := input.FromValue.(*int); ok { - return input.FromValue + if _, ok := in.FromValue.(*int); ok { + return in.FromValue } - v := Int(input.FromValue) + v := Int(in.FromValue) return &v case "int8": - return Int8(input.FromValue) + return Int8(in.FromValue) case "*int8": - if _, ok := input.FromValue.(*int8); ok { - return input.FromValue + if _, ok := in.FromValue.(*int8); ok { + return in.FromValue } - v := Int8(input.FromValue) + v := Int8(in.FromValue) return &v case "int16": - return Int16(input.FromValue) + return Int16(in.FromValue) case "*int16": - if _, ok := input.FromValue.(*int16); ok { - return input.FromValue + if _, ok := in.FromValue.(*int16); ok { + return in.FromValue } - v := Int16(input.FromValue) + v := Int16(in.FromValue) return &v case "int32": - return Int32(input.FromValue) + return Int32(in.FromValue) case "*int32": - if _, ok := input.FromValue.(*int32); ok { - return input.FromValue + if _, ok := in.FromValue.(*int32); ok { + return in.FromValue } - v := Int32(input.FromValue) + v := Int32(in.FromValue) return &v case "int64": - return Int64(input.FromValue) + return Int64(in.FromValue) case "*int64": - if _, ok := input.FromValue.(*int64); ok { - return input.FromValue + if _, ok := in.FromValue.(*int64); ok { + return in.FromValue } - v := Int64(input.FromValue) + v := Int64(in.FromValue) return &v case "uint": - return Uint(input.FromValue) + return Uint(in.FromValue) case "*uint": - if _, ok := input.FromValue.(*uint); ok { - return input.FromValue + if _, ok := in.FromValue.(*uint); ok { + return in.FromValue } - v := Uint(input.FromValue) + v := Uint(in.FromValue) return &v case "uint8": - return Uint8(input.FromValue) + return Uint8(in.FromValue) case "*uint8": - if _, ok := input.FromValue.(*uint8); ok { - return input.FromValue + if _, ok := in.FromValue.(*uint8); ok { + return in.FromValue } - v := Uint8(input.FromValue) + v := Uint8(in.FromValue) return &v case "uint16": - return Uint16(input.FromValue) + return Uint16(in.FromValue) case "*uint16": - if _, ok := input.FromValue.(*uint16); ok { - return input.FromValue + if _, ok := in.FromValue.(*uint16); ok { + return in.FromValue } - v := Uint16(input.FromValue) + v := Uint16(in.FromValue) return &v case "uint32": - return Uint32(input.FromValue) + return Uint32(in.FromValue) case "*uint32": - if _, ok := input.FromValue.(*uint32); ok { - return input.FromValue + if _, ok := in.FromValue.(*uint32); ok { + return in.FromValue } - v := Uint32(input.FromValue) + v := Uint32(in.FromValue) return &v case "uint64": - return Uint64(input.FromValue) + return Uint64(in.FromValue) case "*uint64": - if _, ok := input.FromValue.(*uint64); ok { - return input.FromValue + if _, ok := in.FromValue.(*uint64); ok { + return in.FromValue } - v := Uint64(input.FromValue) + v := Uint64(in.FromValue) return &v case "float32": - return Float32(input.FromValue) + return Float32(in.FromValue) case "*float32": - if _, ok := input.FromValue.(*float32); ok { - return input.FromValue + if _, ok := in.FromValue.(*float32); ok { + return in.FromValue } - v := Float32(input.FromValue) + v := Float32(in.FromValue) return &v case "float64": - return Float64(input.FromValue) + return Float64(in.FromValue) case "*float64": - if _, ok := input.FromValue.(*float64); ok { - return input.FromValue + if _, ok := in.FromValue.(*float64); ok { + return in.FromValue } - v := Float64(input.FromValue) + v := Float64(in.FromValue) return &v case "bool": - return Bool(input.FromValue) + return Bool(in.FromValue) case "*bool": - if _, ok := input.FromValue.(*bool); ok { - return input.FromValue + if _, ok := in.FromValue.(*bool); ok { + return in.FromValue } - v := Bool(input.FromValue) + v := Bool(in.FromValue) return &v case "string": - return String(input.FromValue) + return String(in.FromValue) case "*string": - if _, ok := input.FromValue.(*string); ok { - return input.FromValue + if _, ok := in.FromValue.(*string); ok { + return in.FromValue } - v := String(input.FromValue) + v := String(in.FromValue) return &v case "[]byte": - return Bytes(input.FromValue) + return Bytes(in.FromValue) case "[]int": - return Ints(input.FromValue) + return Ints(in.FromValue) case "[]int32": - return Int32s(input.FromValue) + return Int32s(in.FromValue) case "[]int64": - return Int64s(input.FromValue) + return Int64s(in.FromValue) case "[]uint": - return Uints(input.FromValue) + return Uints(in.FromValue) case "[]uint8": - return Bytes(input.FromValue) + return Bytes(in.FromValue) case "[]uint32": - return Uint32s(input.FromValue) + return Uint32s(in.FromValue) case "[]uint64": - return Uint64s(input.FromValue) + return Uint64s(in.FromValue) case "[]float32": - return Float32s(input.FromValue) + return Float32s(in.FromValue) case "[]float64": - return Float64s(input.FromValue) + return Float64s(in.FromValue) case "[]string": - return Strings(input.FromValue) + return Strings(in.FromValue) case "Time", "time.Time": - if len(input.Extra) > 0 { - return Time(input.FromValue, String(input.Extra[0])) + if len(in.Extra) > 0 { + return Time(in.FromValue, String(in.Extra[0])) } - return Time(input.FromValue) + return Time(in.FromValue) case "*time.Time": var v interface{} - if len(input.Extra) > 0 { - v = Time(input.FromValue, String(input.Extra[0])) + if len(in.Extra) > 0 { + v = Time(in.FromValue, String(in.Extra[0])) } else { - if _, ok := input.FromValue.(*time.Time); ok { - return input.FromValue + if _, ok := in.FromValue.(*time.Time); ok { + return in.FromValue } - v = Time(input.FromValue) + v = Time(in.FromValue) } return &v case "GTime", "gtime.Time": - if len(input.Extra) > 0 { - if v := GTime(input.FromValue, String(input.Extra[0])); v != nil { + 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(input.FromValue); v != nil { + if v := GTime(in.FromValue); v != nil { return *v } else { return *gtime.New() } case "*gtime.Time": - if len(input.Extra) > 0 { - if v := GTime(input.FromValue, String(input.Extra[0])); v != nil { + 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(input.FromValue); v != nil { + if v := GTime(in.FromValue); v != nil { return v } else { return gtime.New() } case "Duration", "time.Duration": - return Duration(input.FromValue) + return Duration(in.FromValue) case "*time.Duration": - if _, ok := input.FromValue.(*time.Duration); ok { - return input.FromValue + if _, ok := in.FromValue.(*time.Duration); ok { + return in.FromValue } - v := Duration(input.FromValue) + v := Duration(in.FromValue) return &v case "map[string]string": - return MapStrStr(input.FromValue) + return MapStrStr(in.FromValue) case "map[string]interface{}": - return Map(input.FromValue) + return Map(in.FromValue) case "[]map[string]interface{}": - return Maps(input.FromValue) + return Maps(in.FromValue) default: - if input.ReferValue != nil { + if in.ReferValue != nil { var ( referReflectValue reflect.Value ) - if v, ok := input.ReferValue.(reflect.Value); ok { + if v, ok := in.ReferValue.(reflect.Value); ok { referReflectValue = v } else { - referReflectValue = reflect.ValueOf(input.ReferValue) + referReflectValue = reflect.ValueOf(in.ReferValue) } - input.ToTypeName = referReflectValue.Kind().String() - input.ReferValue = nil - return reflect.ValueOf(doConvert(input)).Convert(referReflectValue.Type()).Interface() + in.ToTypeName = referReflectValue.Kind().String() + in.ReferValue = nil + return reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface() } - return input.FromValue + return in.FromValue } } @@ -786,3 +786,22 @@ func Float64(any interface{}) float64 { return v } } + +// checkJsonAndUnmarshalUseNumber checks if given `any` is JSON formatted string value and does converting using `json.UnmarshalUseNumber`. +func checkJsonAndUnmarshalUseNumber(any interface{}, target interface{}) bool { + switch r := any.(type) { + case []byte: + if json.Valid(r) { + _ = json.UnmarshalUseNumber(r, &target) + return true + } + + case string: + anyAsBytes := []byte(r) + if json.Valid(anyAsBytes) { + _ = json.UnmarshalUseNumber(anyAsBytes, &target) + return true + } + } + return false +} diff --git a/util/gconv/gconv_interface.go b/util/gconv/gconv_interface.go index 9c23f9425..78bd7911e 100644 --- a/util/gconv/gconv_interface.go +++ b/util/gconv/gconv_interface.go @@ -48,6 +48,11 @@ type apiBytes interface { Bytes() []byte } +// apiInterface is used for type assert api for Interface(). +type apiInterface interface { + Interface() interface{} +} + // apiInterfaces is used for type assert api for Interfaces(). type apiInterfaces interface { Interfaces() []interface{} diff --git a/util/gconv/gconv_maptomap.go b/util/gconv/gconv_maptomap.go index 973f12e52..2c793e5d5 100644 --- a/util/gconv/gconv_maptomap.go +++ b/util/gconv/gconv_maptomap.go @@ -99,7 +99,7 @@ func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]s if e, ok := exception.(errorStack); ok { err = e } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception) } } }() diff --git a/util/gconv/gconv_maptomaps.go b/util/gconv/gconv_maptomaps.go index a3c0928b5..8bfbb0d50 100644 --- a/util/gconv/gconv_maptomaps.go +++ b/util/gconv/gconv_maptomaps.go @@ -119,7 +119,7 @@ func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string] if e, ok := exception.(errorStack); ok { err = e } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception) } } }() diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go index 86dd7ebe9..8d6b07351 100644 --- a/util/gconv/gconv_slice_float.go +++ b/util/gconv/gconv_slice_float.go @@ -118,6 +118,11 @@ func Float32s(any interface{}) []float32 { if v, ok := any.(apiInterfaces); ok { return Float32s(v.Interfaces()) } + // JSON format string value converting. + var result []float32 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -142,6 +147,9 @@ func Float32s(any interface{}) []float32 { return slice default: + if reflectValue.IsZero() { + return []float32{} + } return []float32{Float32(any)} } } @@ -238,6 +246,11 @@ func Float64s(any interface{}) []float64 { if v, ok := any.(apiInterfaces); ok { return Floats(v.Interfaces()) } + // JSON format string value converting. + var result []float64 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -262,6 +275,9 @@ func Float64s(any interface{}) []float64 { return slice default: + if reflectValue.IsZero() { + return []float64{} + } return []float64{Float64(any)} } } diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go index 1195d2053..d27f12095 100644 --- a/util/gconv/gconv_slice_int.go +++ b/util/gconv/gconv_slice_int.go @@ -6,7 +6,9 @@ package gconv -import "reflect" +import ( + "reflect" +) // SliceInt is alias of Ints. func SliceInt(any interface{}) []int { @@ -18,7 +20,7 @@ func SliceInt32(any interface{}) []int32 { return Int32s(any) } -// SliceInt is alias of Int64s. +// SliceInt64 is alias of Int64s. func SliceInt64(any interface{}) []int64 { return Int64s(any) } @@ -30,11 +32,6 @@ func Ints(any interface{}) []int { } var array []int switch value := any.(type) { - case string: - if value == "" { - return []int{} - } - return []int{Int(value)} case []string: array = make([]int, len(value)) for k, v := range value { @@ -123,6 +120,11 @@ func Ints(any interface{}) []int { if v, ok := any.(apiInterfaces); ok { return Ints(v.Interfaces()) } + // JSON format string value converting. + var result []int + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -147,6 +149,9 @@ func Ints(any interface{}) []int { return slice default: + if reflectValue.IsZero() { + return []int{} + } return []int{Int(any)} } } @@ -160,11 +165,6 @@ func Int32s(any interface{}) []int32 { } var array []int32 switch value := any.(type) { - case string: - if value == "" { - return []int32{} - } - return []int32{Int32(value)} case []string: array = make([]int32, len(value)) for k, v := range value { @@ -253,6 +253,11 @@ func Int32s(any interface{}) []int32 { if v, ok := any.(apiInterfaces); ok { return Int32s(v.Interfaces()) } + // JSON format string value converting. + var result []int32 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -277,6 +282,9 @@ func Int32s(any interface{}) []int32 { return slice default: + if reflectValue.IsZero() { + return []int32{} + } return []int32{Int32(any)} } } @@ -290,11 +298,6 @@ func Int64s(any interface{}) []int64 { } var array []int64 switch value := any.(type) { - case string: - if value == "" { - return []int64{} - } - return []int64{Int64(value)} case []string: array = make([]int64, len(value)) for k, v := range value { @@ -383,6 +386,11 @@ func Int64s(any interface{}) []int64 { if v, ok := any.(apiInterfaces); ok { return Int64s(v.Interfaces()) } + // JSON format string value converting. + var result []int64 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -407,6 +415,9 @@ func Int64s(any interface{}) []int64 { return slice default: + if reflectValue.IsZero() { + return []int64{} + } return []int64{Int64(any)} } } diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go index 0d235d529..0c2537c09 100644 --- a/util/gconv/gconv_slice_str.go +++ b/util/gconv/gconv_slice_str.go @@ -6,7 +6,9 @@ package gconv -import "reflect" +import ( + "reflect" +) // SliceStr is alias of Strings. func SliceStr(any interface{}) []string { @@ -104,6 +106,11 @@ func Strings(any interface{}) []string { if v, ok := any.(apiInterfaces); ok { return Strings(v.Interfaces()) } + // JSON format string value converting. + var result []string + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -128,6 +135,9 @@ func Strings(any interface{}) []string { return slice default: + if reflectValue.IsZero() { + return []string{} + } return []string{String(any)} } } diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go index 2cb3321d9..af97802cd 100644 --- a/util/gconv/gconv_slice_uint.go +++ b/util/gconv/gconv_slice_uint.go @@ -119,6 +119,11 @@ func Uints(any interface{}) []uint { if v, ok := any.(apiInterfaces); ok { return Uints(v.Interfaces()) } + // JSON format string value converting. + var result []uint + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -143,6 +148,9 @@ func Uints(any interface{}) []uint { return slice default: + if reflectValue.IsZero() { + return []uint{} + } return []uint{Uint(any)} } } @@ -244,6 +252,11 @@ func Uint32s(any interface{}) []uint32 { if v, ok := any.(apiInterfaces); ok { return Uint32s(v.Interfaces()) } + // JSON format string value converting. + var result []uint32 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -268,6 +281,9 @@ func Uint32s(any interface{}) []uint32 { return slice default: + if reflectValue.IsZero() { + return []uint32{} + } return []uint32{Uint32(any)} } } @@ -369,6 +385,11 @@ func Uint64s(any interface{}) []uint64 { if v, ok := any.(apiInterfaces); ok { return Uint64s(v.Interfaces()) } + // JSON format string value converting. + var result []uint64 + if checkJsonAndUnmarshalUseNumber(any, &result) { + return result + } // Not a common type, it then uses reflection for conversion. var reflectValue reflect.Value if v, ok := value.(reflect.Value); ok { @@ -393,6 +414,9 @@ func Uint64s(any interface{}) []uint64 { return slice default: + if reflectValue.IsZero() { + return []uint64{} + } return []uint64{Uint64(any)} } } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index bb959daa1..886ef857d 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -52,6 +52,42 @@ func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]s return doStruct(params, pointer, keyToAttributeNameMapping, "") } +// doStructWithJsonCheck checks if given `params` is JSON, it then uses json.Unmarshal doing the converting. +func doStructWithJsonCheck(params interface{}, pointer interface{}) (err error, ok bool) { + switch r := params.(type) { + case []byte: + if json.Valid(r) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(r, rv.Interface()), true + } else if rv.CanAddr() { + return json.UnmarshalUseNumber(r, rv.Addr().Interface()), true + } + } else { + return json.UnmarshalUseNumber(r, pointer), true + } + } + case string: + if paramsBytes := []byte(r); json.Valid(paramsBytes) { + if rv, ok := pointer.(reflect.Value); ok { + if rv.Kind() == reflect.Ptr { + return json.UnmarshalUseNumber(paramsBytes, rv.Interface()), true + } else if rv.CanAddr() { + return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()), true + } + } else { + return json.UnmarshalUseNumber(paramsBytes, pointer), true + } + } + default: + // The `params` might be struct that implements interface function Interface, eg: gvar.Var. + if v, ok := params.(apiInterface); ok { + return doStructWithJsonCheck(v.Interface(), pointer) + } + } + return nil, false +} + // doStruct is the core internal converting function for any data to struct. func doStruct(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) { if params == nil { @@ -63,47 +99,28 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string } defer func() { - // Catch the panic, especially the reflect operation panics. + // Catch the panic, especially the reflection operation panics. if exception := recover(); exception != nil { if e, ok := exception.(errorStack); ok { err = e } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception) } } }() - // If given `params` is JSON, it then uses json.Unmarshal doing the converting. - switch r := params.(type) { - case []byte: - if json.Valid(r) { - if rv, ok := pointer.(reflect.Value); ok { - if rv.Kind() == reflect.Ptr { - return json.UnmarshalUseNumber(r, rv.Interface()) - } else if rv.CanAddr() { - return json.UnmarshalUseNumber(r, rv.Addr().Interface()) - } - } else { - return json.UnmarshalUseNumber(r, pointer) - } - } - case string: - if paramsBytes := []byte(r); json.Valid(paramsBytes) { - if rv, ok := pointer.(reflect.Value); ok { - if rv.Kind() == reflect.Ptr { - return json.UnmarshalUseNumber(paramsBytes, rv.Interface()) - } else if rv.CanAddr() { - return json.UnmarshalUseNumber(paramsBytes, rv.Addr().Interface()) - } - } else { - return json.UnmarshalUseNumber(paramsBytes, pointer) - } - } + // JSON content converting. + err, ok := doStructWithJsonCheck(params, pointer) + if err != nil { + return err + } + if ok { + return nil } var ( paramsReflectValue reflect.Value - paramsInterface interface{} // DO NOT use `params` directly as it might be type of `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 pointed element. @@ -129,6 +146,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string } pointerElemReflectValue = pointerReflectValue.Elem() } + // If `params` and `pointer` are the same type, the do directly assignment. // For performance enhancement purpose. if pointerElemReflectValue.IsValid() && pointerElemReflectValue.Type() == paramsReflectValue.Type() { @@ -284,7 +302,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string } // Mark it done. doneMap[attrName] = struct{}{} - if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping, priorityTag); err != nil { + if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping); err != nil { return err } } @@ -292,7 +310,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string } // bindVarToStructAttr sets value to struct object attribute by name. -func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string, priorityTag string) (err error) { +func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string) (err error) { structFieldValue := elem.FieldByName(name) if !structFieldValue.IsValid() { return nil @@ -303,7 +321,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map } defer func() { if exception := recover(); exception != nil { - if err = bindVarToReflectValue(structFieldValue, value, mapping, priorityTag); err != nil { + if err = bindVarToReflectValue(structFieldValue, value, mapping); err != nil { err = gerror.WrapCodef(gcode.CodeInternalError, err, `error binding value to attribute "%s"`, name) } } @@ -323,7 +341,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map return nil } -// bindVarToReflectValueWithInterfaceCheck does binding using common interfaces checks. +// bindVarToReflectValueWithInterfaceCheck does bind using common interfaces checks. func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value interface{}) (err error, ok bool) { var pointer interface{} if reflectValue.Kind() != reflect.Ptr && reflectValue.CanAddr() { @@ -384,10 +402,21 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i } // bindVarToReflectValue sets `value` to reflect value object `structFieldValue`. -func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string, priorityTag string) (err error) { +func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string) (err error) { + // JSON content converting. + err, ok := doStructWithJsonCheck(value, structFieldValue) + if err != nil { + return err + } + if ok { + return nil + } + + // Common interface check. if err, ok := bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok { return err } + kind := structFieldValue.Kind() // Converting using interface, for some kinds. switch kind { @@ -407,7 +436,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma case reflect.Struct: // Recursively converting for struct attribute. - if err := doStruct(value, structFieldValue, nil, ""); err != nil { + if err = doStruct(value, structFieldValue, nil, ""); err != nil { // Note there's reflect conversion mechanism here. structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type())) } @@ -424,14 +453,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma for i := 0; i < v.Len(); i++ { if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { + if err = doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() - if err := doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { + if err = doStruct(v.Index(i).Interface(), e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t)) } @@ -443,17 +472,18 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma a = reflect.MakeSlice(structFieldValue.Type(), 1, 1) t := a.Index(0).Type() if t.Kind() == reflect.Ptr { + // Pointer element. e := reflect.New(t.Elem()).Elem() - if err := doStruct(value, e, nil, ""); err != nil { + if err = doStruct(value, e, nil, ""); err != nil { // Note there's reflect conversion mechanism here. e.Set(reflect.ValueOf(value).Convert(t)) } a.Index(0).Set(e.Addr()) } else { + // Just consider it as struct element. (Although it might be other types but not basic types, eg: map) e := reflect.New(t).Elem() - if err := doStruct(value, e, nil, ""); err != nil { - // Note there's reflect conversion mechanism here. - e.Set(reflect.ValueOf(value).Convert(t)) + if err = doStruct(value, e, nil, ""); err != nil { + return err } a.Index(0).Set(e) } @@ -467,7 +497,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma return err } elem := item.Elem() - if err = bindVarToReflectValue(elem, value, mapping, priorityTag); err == nil { + if err = bindVarToReflectValue(elem, value, mapping); err == nil { structFieldValue.Set(elem.Addr()) } diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go index 2bb370af8..a28e13248 100644 --- a/util/gconv/gconv_structs.go +++ b/util/gconv/gconv_structs.go @@ -62,7 +62,7 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin if e, ok := exception.(errorStack); ok { err = e } else { - err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%v", exception) + err = gerror.NewCodeSkipf(gcode.CodeInternalError, 1, "%+v", exception) } } }() diff --git a/util/gconv/gconv_z_unit_scan_test.go b/util/gconv/gconv_z_unit_scan_test.go index 541baf7dc..281566d72 100644 --- a/util/gconv/gconv_z_unit_scan_test.go +++ b/util/gconv/gconv_z_unit_scan_test.go @@ -7,6 +7,7 @@ package gconv_test import ( + "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/gconv" @@ -193,3 +194,84 @@ func Test_Scan_Maps(t *testing.T) { }) }) } + +func Test_Scan_JsonAttributes(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + 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"` + } + v := gvar.New(` +[ +{"name": "red", "stock": 10, "gift_id": 1, "cost_price": 80, "score_price": 188, "market_price": 188}, +{"name": "blue", "stock": 100, "gift_id": 2, "cost_price": 81, "score_price": 200, "market_price": 288} +]`) + type Product struct { + Skus []Sku + } + var p *Product + err := gconv.Scan(g.Map{ + "Skus": v, + }, &p) + t.AssertNil(err) + t.Assert(len(p.Skus), 2) + + t.Assert(p.Skus[0].Name, "red") + t.Assert(p.Skus[0].Stock, 10) + t.Assert(p.Skus[0].GiftId, 1) + t.Assert(p.Skus[0].CostPrice, 80) + t.Assert(p.Skus[0].ScorePrice, 188) + t.Assert(p.Skus[0].MarketPrice, 188) + + t.Assert(p.Skus[1].Name, "blue") + t.Assert(p.Skus[1].Stock, 100) + t.Assert(p.Skus[1].GiftId, 2) + t.Assert(p.Skus[1].CostPrice, 81) + t.Assert(p.Skus[1].ScorePrice, 200) + t.Assert(p.Skus[1].MarketPrice, 288) + }) +} + +func Test_Scan_JsonAttributes_StringArray(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type S struct { + Array []string + } + var s *S + err := gconv.Scan(g.Map{ + "Array": `["a", "b"]`, + }, &s) + t.AssertNil(err) + t.Assert(len(s.Array), 2) + t.Assert(s.Array[0], "a") + t.Assert(s.Array[1], "b") + }) + + gtest.C(t, func(t *gtest.T) { + type S struct { + Array []string + } + var s *S + err := gconv.Scan(g.Map{ + "Array": `[]`, + }, &s) + t.AssertNil(err) + t.Assert(len(s.Array), 0) + }) + + gtest.C(t, func(t *gtest.T) { + type S struct { + Array []int64 + } + var s *S + err := gconv.Scan(g.Map{ + "Array": `[]`, + }, &s) + t.AssertNil(err) + t.Assert(len(s.Array), 0) + }) +}