From 0e471eab384731cb86a3f900a30a0d015174a73e Mon Sep 17 00:00:00 2001 From: wln32 <49137144+wln32@users.noreply.github.com> Date: Thu, 12 Sep 2024 21:59:38 +0800 Subject: [PATCH] fix(util/gconv): #3764 fix bool converting issue (#3765) --- util/gconv/gconv.go | 45 ++++++++--- util/gconv/gconv_float.go | 76 +++++++++++++++++-- util/gconv/gconv_int.go | 71 +++++++++-------- util/gconv/gconv_uint.go | 67 ++++++++-------- util/gconv/gconv_z_unit_bool_test.go | 16 ++++ .../gconv_z_unit_custom_base_type_test.go | 28 +++++++ util/gconv/gconv_z_unit_float_test.go | 24 +++++- util/gconv/gconv_z_unit_int_test.go | 38 ++++++++++ util/gconv/gconv_z_unit_issue_test.go | 27 +++++++ util/gconv/gconv_z_unit_string_test.go | 1 + util/gconv/gconv_z_unit_uint_test.go | 38 ++++++++++ 11 files changed, 341 insertions(+), 90 deletions(-) create mode 100644 util/gconv/gconv_z_unit_custom_base_type_test.go diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index 91a874555..df93547f8 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -181,10 +181,6 @@ func String(any interface{}) string { } return value.String() default: - // Empty checks. - if value == nil { - return "" - } if f, ok := value.(localinterface.IString); ok { // If the variable implements the String() interface, // then use that interface to perform the conversion @@ -201,11 +197,11 @@ func String(any interface{}) string { kind = rv.Kind() ) switch kind { - case reflect.Chan, + case + reflect.Chan, reflect.Map, reflect.Slice, reflect.Func, - reflect.Ptr, reflect.Interface, reflect.UnsafePointer: if rv.IsNil() { @@ -213,9 +209,21 @@ func String(any interface{}) string { } case reflect.String: return rv.String() - } - if kind == reflect.Ptr { + case reflect.Ptr: + if rv.IsNil() { + return "" + } return String(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Uintptr: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float32, reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'f', -1, 64) + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) } // Finally, we use json.Marshal to convert. if jsonContent, err := json.Marshal(value); err != nil { @@ -252,10 +260,23 @@ func Bool(any interface{}) bool { rv := reflect.ValueOf(any) switch rv.Kind() { case reflect.Ptr: - return !rv.IsNil() - case reflect.Map: - fallthrough - case reflect.Array: + if rv.IsNil() { + return false + } + if rv.Type().Elem().Kind() == reflect.Bool { + return rv.Elem().Bool() + } + return Bool(rv.Elem().Interface()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() != 0 + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() != 0 + case reflect.Float32, reflect.Float64: + return rv.Float() != 0 + case reflect.Bool: + return rv.Bool() + // 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 diff --git a/util/gconv/gconv_float.go b/util/gconv/gconv_float.go index f8ea6a3a6..ceda78df4 100644 --- a/util/gconv/gconv_float.go +++ b/util/gconv/gconv_float.go @@ -7,6 +7,7 @@ package gconv import ( + "reflect" "strconv" "github.com/gogf/gf/v2/encoding/gbinary" @@ -24,13 +25,40 @@ func Float32(any interface{}) float32 { case float64: return float32(value) case []byte: + // TODO: It might panic here for these types. return gbinary.DecodeToFloat32(value) default: - if f, ok := value.(localinterface.IFloat32); ok { - return f.Float32() + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float32(rv.Int()) + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float32(rv.Uint()) + case reflect.Float32, reflect.Float64: + return float32(rv.Float()) + case reflect.Bool: + if rv.Bool() { + return 1 + } + return 0 + case reflect.String: + f, _ := strconv.ParseFloat(rv.String(), 32) + return float32(f) + case reflect.Ptr: + if rv.IsNil() { + return 0 + } + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32() + } + return Float32(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat32); ok { + return f.Float32() + } + v, _ := strconv.ParseFloat(String(any), 32) + return float32(v) } - v, _ := strconv.ParseFloat(String(any), 64) - return float32(v) } } @@ -45,12 +73,44 @@ func Float64(any interface{}) float64 { case float64: return value case []byte: + // TODO: It might panic here for these types. return gbinary.DecodeToFloat64(value) default: - if f, ok := value.(localinterface.IFloat64); ok { - return f.Float64() + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(rv.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return float64(rv.Uint()) + case reflect.Uintptr: + return float64(rv.Uint()) + 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() + case reflect.Bool: + if rv.Bool() { + return 1 + } + return 0 + case reflect.String: + f, _ := strconv.ParseFloat(rv.String(), 64) + return f + case reflect.Ptr: + if rv.IsNil() { + return 0 + } + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64() + } + return Float64(rv.Elem().Interface()) + default: + if f, ok := value.(localinterface.IFloat64); ok { + return f.Float64() + } + v, _ := strconv.ParseFloat(String(any), 64) + return v } - v, _ := strconv.ParseFloat(String(any), 64) - return v } } diff --git a/util/gconv/gconv_int.go b/util/gconv/gconv_int.go index 1596477e2..6ba101d28 100644 --- a/util/gconv/gconv_int.go +++ b/util/gconv/gconv_int.go @@ -8,6 +8,7 @@ package gconv import ( "math" + "reflect" "strconv" "github.com/gogf/gf/v2/encoding/gbinary" @@ -63,44 +64,37 @@ func Int64(any interface{}) int64 { if any == nil { return 0 } - switch value := any.(type) { - case int: - return int64(value) - case int8: - return int64(value) - case int16: - return int64(value) - case int32: - return int64(value) - case int64: - return value - case uint: - return int64(value) - case uint8: - return int64(value) - case uint16: - return int64(value) - case uint32: - return int64(value) - case uint64: - return int64(value) - case float32: - return int64(value) - case float64: - return int64(value) - case bool: - if value { + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return int64(rv.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int64(rv.Uint()) + case reflect.Uintptr: + return int64(rv.Uint()) + case reflect.Float32, reflect.Float64: + return int64(rv.Float()) + case reflect.Bool: + if rv.Bool() { return 1 } return 0 - case []byte: - return gbinary.DecodeToInt64(value) - default: - if f, ok := value.(localinterface.IInt64); ok { + case reflect.Ptr: + if rv.IsNil() { + return 0 + } + if f, ok := any.(localinterface.IInt64); ok { return f.Int64() } + return 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()) + } + case reflect.String: var ( - s = String(value) + s = rv.String() isMinus = false ) if len(s) > 0 { @@ -111,7 +105,7 @@ func Int64(any interface{}) int64 { s = s[1:] } } - // Hexadecimal + // 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 { @@ -120,18 +114,23 @@ func Int64(any interface{}) int64 { return v } } - // Decimal + // Decimal. if v, e := strconv.ParseInt(s, 10, 64); e == nil { if isMinus { return -v } return v } - // Float64 - if valueInt64 := Float64(value); math.IsNaN(valueInt64) { + // Float64. + if valueInt64 := Float64(s); math.IsNaN(valueInt64) { return 0 } else { return int64(valueInt64) } + default: + if f, ok := any.(localinterface.IInt64); ok { + return f.Int64() + } } + return 0 } diff --git a/util/gconv/gconv_uint.go b/util/gconv/gconv_uint.go index 3147832cc..ac1938763 100644 --- a/util/gconv/gconv_uint.go +++ b/util/gconv/gconv_uint.go @@ -8,6 +8,7 @@ package gconv import ( "math" + "reflect" "strconv" "github.com/gogf/gf/v2/encoding/gbinary" @@ -63,43 +64,38 @@ func Uint64(any interface{}) uint64 { if any == nil { return 0 } - switch value := any.(type) { - case int: - return uint64(value) - case int8: - return uint64(value) - case int16: - return uint64(value) - case int32: - return uint64(value) - case int64: - return uint64(value) - case uint: - return uint64(value) - case uint8: - return uint64(value) - case uint16: - return uint64(value) - case uint32: - return uint64(value) - case uint64: - return value - case float32: - return uint64(value) - case float64: - return uint64(value) - case bool: - if value { + rv := reflect.ValueOf(any) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return uint64(rv.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return uint64(rv.Uint()) + case reflect.Uintptr: + return uint64(rv.Uint()) + case reflect.Float32, reflect.Float64: + return uint64(rv.Float()) + case reflect.Bool: + if rv.Bool() { return 1 } return 0 - case []byte: - return gbinary.DecodeToUint64(value) - default: - if f, ok := value.(localinterface.IUint64); ok { + case reflect.Ptr: + if rv.IsNil() { + return 0 + } + if f, ok := any.(localinterface.IUint64); ok { return f.Uint64() } - s := String(value) + return Uint64(rv.Elem().Interface()) + case reflect.Slice: + // TODO:These types should be panic + if rv.Type().Elem().Kind() == reflect.Uint8 { + return gbinary.DecodeToUint64(rv.Bytes()) + } + case reflect.String: + var ( + s = rv.String() + ) // Hexadecimal if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') { if v, e := strconv.ParseUint(s[2:], 16, 64); e == nil { @@ -111,10 +107,15 @@ func Uint64(any interface{}) uint64 { return v } // Float64 - if valueFloat64 := Float64(value); math.IsNaN(valueFloat64) { + if valueFloat64 := Float64(any); math.IsNaN(valueFloat64) { return 0 } else { return uint64(valueFloat64) } + default: + if f, ok := any.(localinterface.IUint64); ok { + return f.Uint64() + } } + return 0 } diff --git a/util/gconv/gconv_z_unit_bool_test.go b/util/gconv/gconv_z_unit_bool_test.go index 51c6f0929..912a2dc2b 100644 --- a/util/gconv/gconv_z_unit_bool_test.go +++ b/util/gconv/gconv_z_unit_bool_test.go @@ -13,6 +13,11 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +var ( + boolTestTrueValue = true + boolTestFalseValue = false +) + var boolTests = []struct { value interface{} expect bool @@ -46,6 +51,17 @@ var boolTests = []struct { {struct{}{}, true}, {&struct{}{}, true}, {nil, false}, + {(*bool)(nil), false}, + + {&boolTestTrueValue, true}, + {&boolTestFalseValue, false}, + + {myBool(true), true}, + {myBool(false), false}, + {(*myBool)(&boolTestTrueValue), true}, + {(*myBool)(&boolTestFalseValue), false}, + + {(*myBool)(nil), false}, } func TestBool(t *testing.T) { diff --git a/util/gconv/gconv_z_unit_custom_base_type_test.go b/util/gconv/gconv_z_unit_custom_base_type_test.go new file mode 100644 index 000000000..e46d09239 --- /dev/null +++ b/util/gconv/gconv_z_unit_custom_base_type_test.go @@ -0,0 +1,28 @@ +// 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_test + +type ( + myInt int + myInt8 int8 + myInt16 int16 + myInt32 int32 + myInt64 int64 + + myUint uint + myUint8 uint8 + myUint16 uint16 + myUint32 uint32 + myUint64 uint64 + + myFloat32 float32 + myFloat64 float64 + + myBool bool + + myString string +) diff --git a/util/gconv/gconv_z_unit_float_test.go b/util/gconv/gconv_z_unit_float_test.go index 5097d1798..df888a3d0 100644 --- a/util/gconv/gconv_z_unit_float_test.go +++ b/util/gconv/gconv_z_unit_float_test.go @@ -16,12 +16,20 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +var ( + // 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. + float32TestValue = float32(123) + float64TestValue = float64(123.456) +) + var floatTests = []struct { value interface{} expect32 float32 expect64 float64 }{ - {true, 0, 0}, + {true, 1, 1}, {false, 0, 0}, {int(0), 0, 0}, @@ -70,9 +78,23 @@ var floatTests = []struct { {struct{}{}, 0, 0}, {nil, 0, 0}, + {(*float32)(nil), 0, 0}, + {(*float64)(nil), 0, 0}, {gvar.New(123), 123, 123}, {gvar.New(123.456), 123.456, 123.456}, + + {&float32TestValue, 123, 123}, + {&float64TestValue, 123.456, 123.456}, + + {myFloat32(123), 123, 123}, + {myFloat64(123.456), 123.456, 123.456}, + + {(*myFloat32)(&float32TestValue), 123, 123}, + {(*myFloat64)(&float64TestValue), 123.456, 123.456}, + + {(*myFloat32)(nil), 0, 0}, + {(*myFloat64)(nil), 0, 0}, } func TestFloat32(t *testing.T) { diff --git a/util/gconv/gconv_z_unit_int_test.go b/util/gconv/gconv_z_unit_int_test.go index 72e2adefb..bf2dda6bc 100644 --- a/util/gconv/gconv_z_unit_int_test.go +++ b/util/gconv/gconv_z_unit_int_test.go @@ -15,6 +15,14 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +var ( + intTestValue = 123 + int8TestValue = int8(123) + int16TestValue = int16(123) + int32TestValue = int32(123) + int64TestValue = int64(123) +) + var intTests = []struct { value interface{} expect int @@ -79,8 +87,38 @@ var intTests = []struct { {struct{}{}, 0, 0, 0, 0, 0}, {nil, 0, 0, 0, 0, 0}, + {(*int)(nil), 0, 0, 0, 0, 0}, + {(*int8)(nil), 0, 0, 0, 0, 0}, + {(*int16)(nil), 0, 0, 0, 0, 0}, + {(*int32)(nil), 0, 0, 0, 0, 0}, + {(*int64)(nil), 0, 0, 0, 0, 0}, + {gvar.New(123), 123, 123, 123, 123, 123}, {gvar.New(123.456), 123, 123, 123, 123, 123}, + + {&intTestValue, 123, 123, 123, 123, 123}, + {&int8TestValue, 123, 123, 123, 123, 123}, + {&int16TestValue, 123, 123, 123, 123, 123}, + {&int32TestValue, 123, 123, 123, 123, 123}, + {&int64TestValue, 123, 123, 123, 123, 123}, + + {(myInt)(intTestValue), 123, 123, 123, 123, 123}, + {(myInt8)(int8TestValue), 123, 123, 123, 123, 123}, + {(myInt16)(int16TestValue), 123, 123, 123, 123, 123}, + {(myInt32)(int32TestValue), 123, 123, 123, 123, 123}, + {(myInt64)(int64TestValue), 123, 123, 123, 123, 123}, + + {(*myInt)(&intTestValue), 123, 123, 123, 123, 123}, + {(*myInt8)(&int8TestValue), 123, 123, 123, 123, 123}, + {(*myInt16)(&int16TestValue), 123, 123, 123, 123, 123}, + {(*myInt32)(&int32TestValue), 123, 123, 123, 123, 123}, + {(*myInt64)(&int64TestValue), 123, 123, 123, 123, 123}, + + {(*myInt)(nil), 0, 0, 0, 0, 0}, + {(*myInt8)(nil), 0, 0, 0, 0, 0}, + {(*myInt16)(nil), 0, 0, 0, 0, 0}, + {(*myInt32)(nil), 0, 0, 0, 0, 0}, + {(*myInt64)(nil), 0, 0, 0, 0, 0}, } func TestInt(t *testing.T) { diff --git a/util/gconv/gconv_z_unit_issue_test.go b/util/gconv/gconv_z_unit_issue_test.go index 7fc0f161e..442f15c8c 100644 --- a/util/gconv/gconv_z_unit_issue_test.go +++ b/util/gconv/gconv_z_unit_issue_test.go @@ -387,3 +387,30 @@ func TestIssue3731(t *testing.T) { t.AssertEQ("", fmt.Sprintf("%T", args.Doc["craft"])) }) } + +// https://github.com/gogf/gf/issues/3764 +func TestIssue3764(t *testing.T) { + type T struct { + True bool `json:"true"` + False bool `json:"false"` + TruePtr *bool `json:"true_ptr"` + FalsePtr *bool `json:"false_ptr"` + } + gtest.C(t, func(t *gtest.T) { + trueValue := true + falseValue := false + m := g.Map{ + "true": trueValue, + "false": falseValue, + "true_ptr": &trueValue, + "false_ptr": &falseValue, + } + tt := &T{} + err := gconv.Struct(m, &tt) + t.AssertNil(err) + t.AssertEQ(tt.True, true) + t.AssertEQ(tt.False, false) + t.AssertEQ(*tt.TruePtr, trueValue) + t.AssertEQ(*tt.FalsePtr, falseValue) + }) +} diff --git a/util/gconv/gconv_z_unit_string_test.go b/util/gconv/gconv_z_unit_string_test.go index 5e704ee57..2d9eaf248 100644 --- a/util/gconv/gconv_z_unit_string_test.go +++ b/util/gconv/gconv_z_unit_string_test.go @@ -65,6 +65,7 @@ var stringTests = []struct { {struct{}{}, "{}"}, {nil, ""}, + {(*string)(nil), ""}, {gvar.New(123), "123"}, {gvar.New(123.456), "123.456"}, diff --git a/util/gconv/gconv_z_unit_uint_test.go b/util/gconv/gconv_z_unit_uint_test.go index 37051c42b..2908a79b9 100644 --- a/util/gconv/gconv_z_unit_uint_test.go +++ b/util/gconv/gconv_z_unit_uint_test.go @@ -15,6 +15,14 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) +var ( + uintTestValue = uint(123) + uint8TestValue = uint8(123) + uint16TestValue = uint16(123) + uint32TestValue = uint32(123) + uint64TestValue = uint64(123) +) + var uintTests = []struct { value interface{} expect uint @@ -76,8 +84,38 @@ var uintTests = []struct { {struct{}{}, 0, 0, 0, 0, 0}, {nil, 0, 0, 0, 0, 0}, + {(*uint)(nil), 0, 0, 0, 0, 0}, + {(*uint8)(nil), 0, 0, 0, 0, 0}, + {(*uint16)(nil), 0, 0, 0, 0, 0}, + {(*uint32)(nil), 0, 0, 0, 0, 0}, + {(*uint64)(nil), 0, 0, 0, 0, 0}, + {gvar.New(123), 123, 123, 123, 123, 123}, {gvar.New(123.456), 123, 123, 123, 123, 123}, + + {&uintTestValue, 123, 123, 123, 123, 123}, + {&uint8TestValue, 123, 123, 123, 123, 123}, + {&uint16TestValue, 123, 123, 123, 123, 123}, + {&uint32TestValue, 123, 123, 123, 123, 123}, + {&uint64TestValue, 123, 123, 123, 123, 123}, + + {(myUint)(uintTestValue), 123, 123, 123, 123, 123}, + {(myUint8)(uint8TestValue), 123, 123, 123, 123, 123}, + {(myUint16)(uint16TestValue), 123, 123, 123, 123, 123}, + {(myUint32)(uint32TestValue), 123, 123, 123, 123, 123}, + {(myUint64)(uint64TestValue), 123, 123, 123, 123, 123}, + + {(*myUint)(&uintTestValue), 123, 123, 123, 123, 123}, + {(*myUint8)(&uint8TestValue), 123, 123, 123, 123, 123}, + {(*myUint16)(&uint16TestValue), 123, 123, 123, 123, 123}, + {(*myUint32)(&uint32TestValue), 123, 123, 123, 123, 123}, + {(*myUint64)(&uint64TestValue), 123, 123, 123, 123, 123}, + + {(*myUint)(nil), 0, 0, 0, 0, 0}, + {(*myUint8)(nil), 0, 0, 0, 0, 0}, + {(*myUint16)(nil), 0, 0, 0, 0, 0}, + {(*myUint32)(nil), 0, 0, 0, 0, 0}, + {(*myUint64)(nil), 0, 0, 0, 0, 0}, } func TestUint(t *testing.T) {