diff --git a/encoding/gjson/gjson.go b/encoding/gjson/gjson.go index 622d45793..0f31e0212 100644 --- a/encoding/gjson/gjson.go +++ b/encoding/gjson/gjson.go @@ -12,6 +12,8 @@ import ( "strconv" "strings" + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/text/gstr" @@ -38,9 +40,14 @@ type Options struct { StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an interface{} as a string instead of as a float64. } -// iInterface is used for type assert api for Interface(). -type iInterface interface { - Interface() interface{} +// iInterfaces is used for type assert api for Interfaces(). +type iInterfaces interface { + Interfaces() []interface{} +} + +// iMapStrAny is the interface support for converting struct parameter to map. +type iMapStrAny interface { + MapStrAny() map[string]interface{} } // setValue sets `value` to `j` by `pattern`. @@ -48,16 +55,14 @@ type iInterface interface { // 1. If value is nil and removed is true, means deleting this value; // 2. It's quite complicated in hierarchical data search, node creating and data assignment; func (j *Json) setValue(pattern string, value interface{}, removed bool) error { - if value != nil { - if utils.IsStruct(value) { - if v, ok := value.(iInterface); ok { - value = v.Interface() - } - } + var ( + err error + array = strings.Split(pattern, string(j.c)) + length = len(array) + ) + if value, err = j.convertValue(value); err != nil { + return gerror.Wrap(err, `Json Set failed`) } - array := strings.Split(pattern, string(j.c)) - length := len(array) - value = j.convertValue(value) // Initialization checks. if *j.p == nil { if gstr.IsNumeric(array[0]) { @@ -252,30 +257,51 @@ done: // convertValue converts `value` to map[string]interface{} or []interface{}, // which can be supported for hierarchical data access. -func (j *Json) convertValue(value interface{}) interface{} { +func (j *Json) convertValue(value interface{}) (convertedValue interface{}, err error) { + if value == nil { + return + } + switch value.(type) { case map[string]interface{}: - return value + return value, nil + case []interface{}: - return value + return value, nil + default: var ( reflectInfo = utils.OriginValueAndKind(value) ) switch reflectInfo.OriginKind { case reflect.Array: - return gconv.Interfaces(value) + return gconv.Interfaces(value), nil + case reflect.Slice: - return gconv.Interfaces(value) + return gconv.Interfaces(value), nil + case reflect.Map: - return gconv.Map(value) + return gconv.Map(value), nil + case reflect.Struct: - return gconv.Map(value) + if v, ok := value.(iMapStrAny); ok { + convertedValue = v.MapStrAny() + } + if utils.IsNil(convertedValue) { + if v, ok := value.(iInterfaces); ok { + convertedValue = v.Interfaces() + } + } + if utils.IsNil(convertedValue) { + convertedValue = gconv.Map(value) + } + if utils.IsNil(convertedValue) { + err = gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported value type "%s"`, reflect.TypeOf(value)) + } + return + default: - // Use json decode/encode at last. - b, _ := Encode(value) - v, _ := Decode(b) - return v + return value, nil } } } diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index f92b14564..ed6e96380 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -106,12 +106,26 @@ func (j *Json) Set(pattern string, value interface{}) error { return j.setValue(pattern, value, false) } +// MustSet performs as Set, but it panics if any error occurs. +func (j *Json) MustSet(pattern string, value interface{}) { + if err := j.Set(pattern, value); err != nil { + panic(err) + } +} + // Remove deletes value with specified `pattern`. // It supports hierarchical data access by char separator, which is '.' in default. func (j *Json) Remove(pattern string) error { return j.setValue(pattern, nil, true) } +// MustRemove performs as Remove, but it panics if any error occurs. +func (j *Json) MustRemove(pattern string) { + if err := j.Remove(pattern); err != nil { + panic(err) + } +} + // Contains checks whether the value by specified `pattern` exist. func (j *Json) Contains(pattern string) bool { return j.Get(pattern) != nil @@ -155,6 +169,13 @@ func (j *Json) Append(pattern string, value interface{}) error { return gerror.NewCodef(gcode.CodeInvalidParameter, "invalid variable type of %s", pattern) } +// MustAppend performs as Append, but it panics if any error occurs. +func (j *Json) MustAppend(pattern string, value interface{}) { + if err := j.Append(pattern, value); err != nil { + panic(err) + } +} + // Map converts current Json object to map[string]interface{}. // It returns nil if fails. func (j *Json) Map() map[string]interface{} { diff --git a/encoding/gjson/gjson_implements.go b/encoding/gjson/gjson_implements.go index c621482e9..a907e03f6 100644 --- a/encoding/gjson/gjson_implements.go +++ b/encoding/gjson/gjson_implements.go @@ -29,3 +29,13 @@ func (j *Json) UnmarshalValue(value interface{}) error { } return nil } + +// MapStrAny implements interface function MapStrAny(). +func (j *Json) MapStrAny() map[string]interface{} { + return j.Map() +} + +// Interfaces implements interface function Interfaces(). +func (j *Json) Interfaces() []interface{} { + return j.Array() +} diff --git a/encoding/gjson/gjson_z_unit_feature_set_test.go b/encoding/gjson/gjson_z_unit_feature_set_test.go index c1d22926c..003682a5d 100644 --- a/encoding/gjson/gjson_z_unit_feature_set_test.go +++ b/encoding/gjson/gjson_z_unit_feature_set_test.go @@ -10,6 +10,7 @@ import ( "bytes" "testing" + "github.com/gogf/gf/v2/container/garray" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/test/gtest" @@ -329,3 +330,12 @@ func Test_Set20(t *testing.T) { ), true) }) } + +func Test_Set_GArray(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + j := gjson.New(nil) + arr := garray.New().Append("test") + t.AssertNil(j.Set("arr", arr)) + t.Assert(j.Get("arr").Array(), g.Slice{"test"}) + }) +} diff --git a/encoding/gjson/gjson_z_unit_test.go b/encoding/gjson/gjson_z_unit_test.go index dfdc2cc55..a77eacd29 100644 --- a/encoding/gjson/gjson_z_unit_test.go +++ b/encoding/gjson/gjson_z_unit_test.go @@ -510,8 +510,8 @@ func TestJson_Set_With_Struct(t *testing.T) { "user3": g.Map{"name": "user3"}, }) user1 := v.GetJson("user1") - user1.Set("id", 111) - v.Set("user1", user1) + t.AssertNil(user1.Set("id", 111)) + t.AssertNil(v.Set("user1", user1)) t.Assert(v.Get("user1.id"), 111) }) } diff --git a/errors/gerror/gerror.go b/errors/gerror/gerror.go index 85b80b465..09ddedc18 100644 --- a/errors/gerror/gerror.go +++ b/errors/gerror/gerror.go @@ -84,8 +84,8 @@ func NewSkipf(skip int, format string, args ...interface{}) error { } } -// Wrap wraps error with text. -// It returns nil if given err is nil. +// Wrap wraps error with text. It returns nil if given err is nil. +// Note that it does not lose the error code of wrapped error, as it inherits the error code from it. func Wrap(err error, text string) error { if err == nil { return nil @@ -98,9 +98,9 @@ func Wrap(err error, text string) error { } } -// Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is called, and the format specifier. +// Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier. // It returns nil if given `err` is nil. +// Note that it does not lose the error code of wrapped error, as it inherits the error code from it. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil @@ -113,9 +113,9 @@ func Wrapf(err error, format string, args ...interface{}) error { } } -// WrapSkip wraps error with text. -// It returns nil if given err is nil. +// WrapSkip wraps error with text. It returns nil if given err is nil. // The parameter `skip` specifies the stack callers skipped amount. +// Note that it does not lose the error code of wrapped error, as it inherits the error code from it. func WrapSkip(skip int, err error, text string) error { if err == nil { return nil @@ -128,9 +128,9 @@ func WrapSkip(skip int, err error, text string) error { } } -// WrapSkipf wraps error with text that is formatted with given format and args. -// It returns nil if given err is nil. +// WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil. // The parameter `skip` specifies the stack callers skipped amount. +// Note that it does not lose the error code of wrapped error, as it inherits the error code from it. func WrapSkipf(skip int, err error, format string, args ...interface{}) error { if err == nil { return nil diff --git a/internal/empty/empty.go b/internal/empty/empty.go index 936065922..7ee877b51 100644 --- a/internal/empty/empty.go +++ b/internal/empty/empty.go @@ -298,7 +298,7 @@ func IsEmpty(value interface{}) bool { // return false //} -// IsNil checks whether given `value` is nil. +// IsNil checks whether given `value` is nil, especially for interface{} type value. // Parameter `traceSource` is used for tracing to the source variable if given `value` is type of pinter // that also points to a pointer. It returns nil if the source is nil when `traceSource` is true. // Note that it might use reflect feature which affects performance a little. diff --git a/internal/utils/utils_is.go b/internal/utils/utils_is.go index b9655b633..778d45ed9 100644 --- a/internal/utils/utils_is.go +++ b/internal/utils/utils_is.go @@ -12,7 +12,7 @@ import ( "github.com/gogf/gf/v2/internal/empty" ) -// IsNil checks whether `value` is nil. +// IsNil checks whether `value` is nil, especially for interface{} type value. func IsNil(value interface{}) bool { return empty.IsNil(value) } diff --git a/text/gstr/gstr_case.go b/text/gstr/gstr_case.go index 0f70f27b9..c07fff726 100644 --- a/text/gstr/gstr_case.go +++ b/text/gstr/gstr_case.go @@ -69,7 +69,7 @@ func CaseSnakeFirstUpper(word string, underscore ...string) string { } for { - m := firstCamelCaseStart.FindAllStringSubmatch(word, 1) + m = firstCamelCaseStart.FindAllStringSubmatch(word, 1) if len(m) > 0 && m[0][1] != "" { w := strings.ToLower(m[0][1]) w = w[:len(w)-1] + replace + string(w[len(w)-1]) diff --git a/text/gstr/gstr_replace.go b/text/gstr/gstr_replace.go index fe6ade3b5..713fc02c7 100644 --- a/text/gstr/gstr_replace.go +++ b/text/gstr/gstr_replace.go @@ -7,8 +7,9 @@ package gstr import ( - "github.com/gogf/gf/v2/internal/utils" "strings" + + "github.com/gogf/gf/v2/internal/utils" ) // Replace returns a copy of the string `origin` @@ -32,13 +33,21 @@ func ReplaceI(origin, search, replace string, count ...int) string { return origin } var ( - length = len(search) - searchLower = strings.ToLower(search) + searchLength = len(search) + searchLower = strings.ToLower(search) + originLower string + pos int + diff = len(replace) - searchLength ) for { - originLower := strings.ToLower(origin) - if pos := strings.Index(originLower, searchLower); pos != -1 { - origin = origin[:pos] + replace + origin[pos+length:] + originLower = strings.ToLower(origin) + if pos = Pos(originLower, searchLower, pos); pos != -1 { + origin = origin[:pos] + replace + origin[pos+searchLength:] + if diff < 0 { + pos += -diff + } else { + pos += diff + 1 + } if n--; n == 0 { break } diff --git a/text/gstr/gstr_z_unit_replace_test.go b/text/gstr/gstr_z_unit_replace_test.go new file mode 100644 index 000000000..81e049f37 --- /dev/null +++ b/text/gstr/gstr_z_unit_replace_test.go @@ -0,0 +1,90 @@ +// 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. + +// go test *.go -bench=".*" + +package gstr_test + +import ( + "testing" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" +) + +func Test_Replace(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + s1 := "abcdEFG乱入的中文abcdefg" + t.Assert(gstr.Replace(s1, "ab", "AB"), "ABcdEFG乱入的中文ABcdefg") + t.Assert(gstr.Replace(s1, "EF", "ef"), "abcdefG乱入的中文abcdefg") + t.Assert(gstr.Replace(s1, "MN", "mn"), s1) + + t.Assert(gstr.ReplaceByArray(s1, g.ArrayStr{ + "a", "A", + "A", "-", + "a", + }), "-bcdEFG乱入的中文-bcdefg") + + t.Assert(gstr.ReplaceByMap(s1, g.MapStrStr{ + "a": "A", + "G": "g", + }), "AbcdEFg乱入的中文Abcdefg") + }) +} + +func Test_ReplaceI_1(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + s1 := "abcd乱入的中文ABCD" + s2 := "a" + t.Assert(gstr.ReplaceI(s1, "ab", "aa"), "aacd乱入的中文aaCD") + t.Assert(gstr.ReplaceI(s1, "ab", "aa", 0), "abcd乱入的中文ABCD") + t.Assert(gstr.ReplaceI(s1, "ab", "aa", 1), "aacd乱入的中文ABCD") + + t.Assert(gstr.ReplaceI(s1, "abcd", "-"), "-乱入的中文-") + t.Assert(gstr.ReplaceI(s1, "abcd", "-", 1), "-乱入的中文ABCD") + + t.Assert(gstr.ReplaceI(s1, "abcd乱入的", ""), "中文ABCD") + t.Assert(gstr.ReplaceI(s1, "ABCD乱入的", ""), "中文ABCD") + + t.Assert(gstr.ReplaceI(s2, "A", "-"), "-") + t.Assert(gstr.ReplaceI(s2, "a", "-"), "-") + + t.Assert(gstr.ReplaceIByArray(s1, g.ArrayStr{ + "abcd乱入的", "-", + "-", "=", + "a", + }), "=中文ABCD") + + t.Assert(gstr.ReplaceIByMap(s1, g.MapStrStr{ + "ab": "-", + "CD": "=", + }), "-=乱入的中文-=") + }) +} + +func Test_ReplaceI_2(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.ReplaceI("aaa", "A", "-a-"), `-a--a--a-`) + t.Assert(gstr.ReplaceI("aaaa", "AA", "-"), `--`) + t.Assert(gstr.ReplaceI("a a a", "A", "b"), `b b b`) + t.Assert(gstr.ReplaceI("aaaaaa", "aa", "a"), `aaa`) + t.Assert(gstr.ReplaceI("aaaaaa", "AA", "A"), `AAA`) + t.Assert(gstr.ReplaceI("aaa", "A", "AA"), `AAAAAA`) + t.Assert(gstr.ReplaceI("aaa", "A", "AA"), `AAAAAA`) + t.Assert(gstr.ReplaceI("a duration", "duration", "recordduration"), `a recordduration`) + }) + // With count parameter. + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.ReplaceI("aaaaaa", "aa", "a", 2), `aaaa`) + t.Assert(gstr.ReplaceI("aaaaaa", "AA", "A", 1), `Aaaaa`) + t.Assert(gstr.ReplaceI("aaaaaa", "AA", "A", 3), `AAA`) + t.Assert(gstr.ReplaceI("aaaaaa", "AA", "A", 4), `AAA`) + t.Assert(gstr.ReplaceI("aaa", "A", "AA", 2), `AAAAa`) + t.Assert(gstr.ReplaceI("aaa", "A", "AA", 3), `AAAAAA`) + t.Assert(gstr.ReplaceI("aaa", "A", "AA", 4), `AAAAAA`) + }) +} diff --git a/text/gstr/gstr_z_unit_basic_test.go b/text/gstr/gstr_z_unit_test.go similarity index 89% rename from text/gstr/gstr_z_unit_basic_test.go rename to text/gstr/gstr_z_unit_test.go index 19ffeedc9..5744d3ed0 100644 --- a/text/gstr/gstr_z_unit_basic_test.go +++ b/text/gstr/gstr_z_unit_test.go @@ -11,61 +11,10 @@ package gstr_test import ( "testing" - "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/text/gstr" ) -func Test_Replace(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - s1 := "abcdEFG乱入的中文abcdefg" - t.Assert(gstr.Replace(s1, "ab", "AB"), "ABcdEFG乱入的中文ABcdefg") - t.Assert(gstr.Replace(s1, "EF", "ef"), "abcdefG乱入的中文abcdefg") - t.Assert(gstr.Replace(s1, "MN", "mn"), s1) - - t.Assert(gstr.ReplaceByArray(s1, g.ArrayStr{ - "a", "A", - "A", "-", - "a", - }), "-bcdEFG乱入的中文-bcdefg") - - t.Assert(gstr.ReplaceByMap(s1, g.MapStrStr{ - "a": "A", - "G": "g", - }), "AbcdEFg乱入的中文Abcdefg") - }) -} - -func Test_ReplaceI_1(t *testing.T) { - gtest.C(t, func(t *gtest.T) { - s1 := "abcd乱入的中文ABCD" - s2 := "a" - t.Assert(gstr.ReplaceI(s1, "ab", "aa"), "aacd乱入的中文aaCD") - t.Assert(gstr.ReplaceI(s1, "ab", "aa", 0), "abcd乱入的中文ABCD") - t.Assert(gstr.ReplaceI(s1, "ab", "aa", 1), "aacd乱入的中文ABCD") - - t.Assert(gstr.ReplaceI(s1, "abcd", "-"), "-乱入的中文-") - t.Assert(gstr.ReplaceI(s1, "abcd", "-", 1), "-乱入的中文ABCD") - - t.Assert(gstr.ReplaceI(s1, "abcd乱入的", ""), "中文ABCD") - t.Assert(gstr.ReplaceI(s1, "ABCD乱入的", ""), "中文ABCD") - - t.Assert(gstr.ReplaceI(s2, "A", "-"), "-") - t.Assert(gstr.ReplaceI(s2, "a", "-"), "-") - - t.Assert(gstr.ReplaceIByArray(s1, g.ArrayStr{ - "abcd乱入的", "-", - "-", "=", - "a", - }), "=中文ABCD") - - t.Assert(gstr.ReplaceIByMap(s1, g.MapStrStr{ - "ab": "-", - "CD": "=", - }), "-=乱入的中文-=") - }) -} - func Test_ToLower(t *testing.T) { gtest.C(t, func(t *gtest.T) { s1 := "abcdEFG乱入的中文abcdefg" diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go index e7e1569fe..47754a3df 100644 --- a/util/gconv/gconv.go +++ b/util/gconv/gconv.go @@ -306,8 +306,10 @@ func Bytes(any interface{}) []byte { switch value := any.(type) { case string: return []byte(value) + case []byte: return value + default: if f, ok := value.(iBytes); ok { return f.Bytes()