improve package gjson

This commit is contained in:
John Guo 2021-12-20 00:32:23 +08:00
parent de17302ad0
commit 7e81600772
9 changed files with 104 additions and 35 deletions

View File

@ -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
}
}
}

View File

@ -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{} {

View File

@ -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()
}

View File

@ -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"})
})
}

View File

@ -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)
})
}

View File

@ -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

View File

@ -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.

View File

@ -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)
}

View File

@ -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()