mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.
**Type alias migration to `any`:**
* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)
**Generated code and templates:**
* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
**Container and utility updates:**
* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)
These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
473 lines
12 KiB
Go
473 lines
12 KiB
Go
// 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 gjson provides convenient API for JSON/XML/INI/YAML/TOML data handling.
|
|
package gjson
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/internal/reflection"
|
|
"github.com/gogf/gf/v2/internal/rwmutex"
|
|
"github.com/gogf/gf/v2/internal/utils"
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
type ContentType string
|
|
|
|
const (
|
|
ContentTypeJSON ContentType = `json`
|
|
ContentTypeJs ContentType = `js`
|
|
ContentTypeXML ContentType = `xml`
|
|
ContentTypeIni ContentType = `ini`
|
|
ContentTypeYaml ContentType = `yaml`
|
|
ContentTypeYml ContentType = `yml`
|
|
ContentTypeToml ContentType = `toml`
|
|
ContentTypeProperties ContentType = `properties`
|
|
)
|
|
|
|
const (
|
|
defaultSplitChar = '.' // Separator char for hierarchical data access.
|
|
)
|
|
|
|
// Json is the customized JSON struct.
|
|
type Json struct {
|
|
mu rwmutex.RWMutex
|
|
p *any // Pointer for hierarchical data access, it's the root of data in default.
|
|
c byte // Char separator('.' in default).
|
|
vc bool // Violence Check(false in default), which is used to access data when the hierarchical data key contains separator char.
|
|
}
|
|
|
|
// Options for Json object creating/loading.
|
|
type Options struct {
|
|
Safe bool // Mark this object is for in concurrent-safe usage. This is especially for Json object creating.
|
|
Tags string // Custom priority tags for decoding, eg: "json,yaml,MyTag". This is especially for struct parsing into Json object.
|
|
Type ContentType // Type specifies the data content type, eg: json, xml, yaml, toml, ini.
|
|
StrNumber bool // StrNumber causes the Decoder to unmarshal a number into an any as a string instead of as a float64.
|
|
}
|
|
|
|
// iInterfaces is used for type assert api for Interfaces().
|
|
type iInterfaces interface {
|
|
Interfaces() []any
|
|
}
|
|
|
|
// iMapStrAny is the interface support for converting struct parameter to map.
|
|
type iMapStrAny interface {
|
|
MapStrAny() map[string]any
|
|
}
|
|
|
|
// iVal is the interface for underlying any retrieving.
|
|
type iVal interface {
|
|
Val() any
|
|
}
|
|
|
|
// setValue sets `value` to `j` by `pattern`.
|
|
// Note:
|
|
// 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 any, removed bool) error {
|
|
var (
|
|
err error
|
|
array = strings.Split(pattern, string(j.c))
|
|
length = len(array)
|
|
)
|
|
if value, err = j.convertValue(value); err != nil {
|
|
return err
|
|
}
|
|
// Initialization checks.
|
|
if *j.p == nil {
|
|
if gstr.IsNumeric(array[0]) {
|
|
*j.p = make([]any, 0)
|
|
} else {
|
|
*j.p = make(map[string]any)
|
|
}
|
|
}
|
|
var (
|
|
pparent *any = nil // Parent pointer.
|
|
pointer = j.p // Current pointer.
|
|
)
|
|
j.mu.Lock()
|
|
defer j.mu.Unlock()
|
|
for i := 0; i < length; i++ {
|
|
switch (*pointer).(type) {
|
|
case map[string]any:
|
|
if i == length-1 {
|
|
if removed && value == nil {
|
|
// Delete item from map.
|
|
delete((*pointer).(map[string]any), array[i])
|
|
} else {
|
|
if (*pointer).(map[string]any) == nil {
|
|
*pointer = map[string]any{}
|
|
}
|
|
(*pointer).(map[string]any)[array[i]] = value
|
|
}
|
|
} else {
|
|
// If the key does not exit in the map.
|
|
if v, ok := (*pointer).(map[string]any)[array[i]]; !ok {
|
|
if removed && value == nil {
|
|
goto done
|
|
}
|
|
// Creating new node.
|
|
if gstr.IsNumeric(array[i+1]) {
|
|
// Creating array node.
|
|
n, _ := strconv.Atoi(array[i+1])
|
|
var v any = make([]any, n+1)
|
|
pparent = j.setPointerWithValue(pointer, array[i], v)
|
|
pointer = &v
|
|
} else {
|
|
// Creating map node.
|
|
var v any = make(map[string]any)
|
|
pparent = j.setPointerWithValue(pointer, array[i], v)
|
|
pointer = &v
|
|
}
|
|
} else {
|
|
pparent = pointer
|
|
pointer = &v
|
|
}
|
|
}
|
|
|
|
case []any:
|
|
// A string key.
|
|
if !gstr.IsNumeric(array[i]) {
|
|
if i == length-1 {
|
|
*pointer = map[string]any{array[i]: value}
|
|
} else {
|
|
var v any = make(map[string]any)
|
|
*pointer = v
|
|
pparent = pointer
|
|
pointer = &v
|
|
}
|
|
continue
|
|
}
|
|
// Numeric index.
|
|
valueNum, err := strconv.Atoi(array[i])
|
|
if err != nil {
|
|
err = gerror.WrapCodef(gcode.CodeInvalidParameter, err, `strconv.Atoi failed for string "%s"`, array[i])
|
|
return err
|
|
}
|
|
|
|
if i == length-1 {
|
|
// Leaf node.
|
|
if len((*pointer).([]any)) > valueNum {
|
|
if removed && value == nil {
|
|
// Deleting element.
|
|
if pparent == nil {
|
|
*pointer = append((*pointer).([]any)[:valueNum], (*pointer).([]any)[valueNum+1:]...)
|
|
} else {
|
|
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]any)[:valueNum], (*pointer).([]any)[valueNum+1:]...))
|
|
}
|
|
} else {
|
|
(*pointer).([]any)[valueNum] = value
|
|
}
|
|
} else {
|
|
if removed && value == nil {
|
|
goto done
|
|
}
|
|
if pparent == nil {
|
|
// It is the root node.
|
|
j.setPointerWithValue(pointer, array[i], value)
|
|
} else {
|
|
// It is not the root node.
|
|
s := make([]any, valueNum+1)
|
|
copy(s, (*pointer).([]any))
|
|
s[valueNum] = value
|
|
j.setPointerWithValue(pparent, array[i-1], s)
|
|
}
|
|
}
|
|
} else {
|
|
// Branch node.
|
|
if gstr.IsNumeric(array[i+1]) {
|
|
n, _ := strconv.Atoi(array[i+1])
|
|
pSlice := (*pointer).([]any)
|
|
if len(pSlice) > valueNum {
|
|
item := pSlice[valueNum]
|
|
if s, ok := item.([]any); ok {
|
|
for i := 0; i < n-len(s); i++ {
|
|
s = append(s, nil)
|
|
}
|
|
pparent = pointer
|
|
pointer = &pSlice[valueNum]
|
|
} else {
|
|
if removed && value == nil {
|
|
goto done
|
|
}
|
|
var v any = make([]any, n+1)
|
|
pparent = j.setPointerWithValue(pointer, array[i], v)
|
|
pointer = &v
|
|
}
|
|
} else {
|
|
if removed && value == nil {
|
|
goto done
|
|
}
|
|
var v any = make([]any, n+1)
|
|
pparent = j.setPointerWithValue(pointer, array[i], v)
|
|
pointer = &v
|
|
}
|
|
} else {
|
|
pSlice := (*pointer).([]any)
|
|
if len(pSlice) > valueNum {
|
|
pparent = pointer
|
|
pointer = &(*pointer).([]any)[valueNum]
|
|
} else {
|
|
s := make([]any, valueNum+1)
|
|
copy(s, pSlice)
|
|
s[valueNum] = make(map[string]any)
|
|
if pparent != nil {
|
|
// i > 0
|
|
j.setPointerWithValue(pparent, array[i-1], s)
|
|
pparent = pointer
|
|
pointer = &s[valueNum]
|
|
} else {
|
|
// i = 0
|
|
var v any = s
|
|
*pointer = v
|
|
pparent = pointer
|
|
pointer = &s[valueNum]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the variable pointed to by the `pointer` is not of a reference type,
|
|
// then it modifies the variable via its the parent, ie: pparent.
|
|
default:
|
|
if removed && value == nil {
|
|
goto done
|
|
}
|
|
if gstr.IsNumeric(array[i]) {
|
|
n, _ := strconv.Atoi(array[i])
|
|
s := make([]any, n+1)
|
|
if i == length-1 {
|
|
s[n] = value
|
|
}
|
|
if pparent != nil {
|
|
pparent = j.setPointerWithValue(pparent, array[i-1], s)
|
|
} else {
|
|
*pointer = s
|
|
pparent = pointer
|
|
}
|
|
} else {
|
|
var v1, v2 any
|
|
if i == length-1 {
|
|
v1 = map[string]any{
|
|
array[i]: value,
|
|
}
|
|
} else {
|
|
v1 = map[string]any{
|
|
array[i]: nil,
|
|
}
|
|
}
|
|
if pparent != nil {
|
|
pparent = j.setPointerWithValue(pparent, array[i-1], v1)
|
|
} else {
|
|
*pointer = v1
|
|
pparent = pointer
|
|
}
|
|
v2 = v1.(map[string]any)[array[i]]
|
|
pointer = &v2
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
return nil
|
|
}
|
|
|
|
// convertValue converts `value` to map[string]any or []any,
|
|
// which can be supported for hierarchical data access.
|
|
func (j *Json) convertValue(value any) (convertedValue any, err error) {
|
|
if value == nil {
|
|
return
|
|
}
|
|
|
|
switch value.(type) {
|
|
case map[string]any:
|
|
return value, nil
|
|
|
|
case []any:
|
|
return value, nil
|
|
|
|
default:
|
|
var (
|
|
reflectInfo = reflection.OriginValueAndKind(value)
|
|
)
|
|
switch reflectInfo.OriginKind {
|
|
case reflect.Array:
|
|
return gconv.Interfaces(value), nil
|
|
|
|
case reflect.Slice:
|
|
return gconv.Interfaces(value), nil
|
|
|
|
case reflect.Map:
|
|
return gconv.Map(value), nil
|
|
|
|
case reflect.Struct:
|
|
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:
|
|
return value, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// setPointerWithValue sets `key`:`value` to `pointer`, the `key` may be a map key or slice index.
|
|
// It returns the pointer to the new value set.
|
|
func (j *Json) setPointerWithValue(pointer *any, key string, value any) *any {
|
|
switch (*pointer).(type) {
|
|
case map[string]any:
|
|
(*pointer).(map[string]any)[key] = value
|
|
return &value
|
|
case []any:
|
|
n, _ := strconv.Atoi(key)
|
|
if len((*pointer).([]any)) > n {
|
|
(*pointer).([]any)[n] = value
|
|
return &(*pointer).([]any)[n]
|
|
} else {
|
|
s := make([]any, n+1)
|
|
copy(s, (*pointer).([]any))
|
|
s[n] = value
|
|
*pointer = s
|
|
return &s[n]
|
|
}
|
|
default:
|
|
*pointer = value
|
|
}
|
|
return pointer
|
|
}
|
|
|
|
// getPointerByPattern returns a pointer to the value by specified `pattern`.
|
|
func (j *Json) getPointerByPattern(pattern string) *any {
|
|
if j.p == nil {
|
|
return nil
|
|
}
|
|
if j.vc {
|
|
return j.getPointerByPatternWithViolenceCheck(pattern)
|
|
} else {
|
|
return j.getPointerByPatternWithoutViolenceCheck(pattern)
|
|
}
|
|
}
|
|
|
|
// getPointerByPatternWithViolenceCheck returns a pointer to the value of specified `pattern` with violence check.
|
|
func (j *Json) getPointerByPatternWithViolenceCheck(pattern string) *any {
|
|
if !j.vc {
|
|
return j.getPointerByPatternWithoutViolenceCheck(pattern)
|
|
}
|
|
|
|
// It returns nil if pattern is empty.
|
|
if pattern == "" {
|
|
return nil
|
|
}
|
|
// It returns all if pattern is ".".
|
|
if pattern == "." {
|
|
return j.p
|
|
}
|
|
|
|
var (
|
|
index = len(pattern)
|
|
start = 0
|
|
length = 0
|
|
pointer = j.p
|
|
)
|
|
if index == 0 {
|
|
return pointer
|
|
}
|
|
for {
|
|
if r := j.checkPatternByPointer(pattern[start:index], pointer); r != nil {
|
|
if length += index - start; start > 0 {
|
|
length += 1
|
|
}
|
|
start = index + 1
|
|
index = len(pattern)
|
|
if length == len(pattern) {
|
|
return r
|
|
} else {
|
|
pointer = r
|
|
}
|
|
} else {
|
|
// Get the position for next separator char.
|
|
index = strings.LastIndexByte(pattern[start:index], j.c)
|
|
if index != -1 && length > 0 {
|
|
index += length + 1
|
|
}
|
|
}
|
|
if start >= index {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getPointerByPatternWithoutViolenceCheck returns a pointer to the value of specified `pattern`, with no violence check.
|
|
func (j *Json) getPointerByPatternWithoutViolenceCheck(pattern string) *any {
|
|
if j.vc {
|
|
return j.getPointerByPatternWithViolenceCheck(pattern)
|
|
}
|
|
|
|
// It returns nil if pattern is empty.
|
|
if pattern == "" {
|
|
return nil
|
|
}
|
|
// It returns all if pattern is ".".
|
|
if pattern == "." {
|
|
return j.p
|
|
}
|
|
|
|
pointer := j.p
|
|
if len(pattern) == 0 {
|
|
return pointer
|
|
}
|
|
array := strings.Split(pattern, string(j.c))
|
|
for k, v := range array {
|
|
if r := j.checkPatternByPointer(v, pointer); r != nil {
|
|
if k == len(array)-1 {
|
|
return r
|
|
} else {
|
|
pointer = r
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkPatternByPointer checks whether there's value by `key` in specified `pointer`.
|
|
// It returns a pointer to the value.
|
|
func (j *Json) checkPatternByPointer(key string, pointer *any) *any {
|
|
switch (*pointer).(type) {
|
|
case map[string]any:
|
|
if v, ok := (*pointer).(map[string]any)[key]; ok {
|
|
return &v
|
|
}
|
|
case []any:
|
|
if gstr.IsNumeric(key) {
|
|
n, err := strconv.Atoi(key)
|
|
if err == nil && len((*pointer).([]any)) > n {
|
|
return &(*pointer).([]any)[n]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|