feat: implement high-performance FastEncoder and FastDecoder with frictionless mapping and desensitization support (v1.1.0) (by AI)
This commit is contained in:
parent
72e6f54df0
commit
cb458129fb
@ -1,5 +1,12 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## [v1.1.0] - 2026-05-02
|
||||||
|
- **功能**: 新增 `FastEncoder`,实现单路径 JSON 编码,大幅提升性能并减少内存分配。
|
||||||
|
- **功能**: 新增 `ToJSONDesensitize` 和 `ToJSONDesensitizeBytes`,支持原生字段脱敏。
|
||||||
|
- **功能**: 新增 `FastDecoder`,实现单路径流式 JSON 解析,支持“零摩擦” Key 匹配(大小写不敏感、归一化映射)。
|
||||||
|
- **优化**: 完善 `null` 值处理逻辑,区分 `nil` 指针与空 `slice`/`map`。
|
||||||
|
- **重构**: 移除旧版 `makeJSONType` 等冗余逻辑,代码结构更简洁高效。
|
||||||
|
|
||||||
## [v1.0.4] - 2026-04-30
|
## [v1.0.4] - 2026-04-30
|
||||||
- **优化**: 重构 `UniqueAppend`,改用 Map 查重,性能提升至 $O(n)$。
|
- **优化**: 重构 `UniqueAppend`,改用 Map 查重,性能提升至 $O(n)$。
|
||||||
- **优化**: 提升 `If` 函数参数描述性,符合工程规范。
|
- **优化**: 提升 `If` 函数参数描述性,符合工程规范。
|
||||||
|
|||||||
@ -61,8 +61,8 @@ cast.Split("a, b, ", ",") // ["a", "b"],去除空白字符
|
|||||||
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速索引化
|
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速索引化
|
||||||
|
|
||||||
4. **序列化(JSON & YAML)**
|
4. **序列化(JSON & YAML)**
|
||||||
* **JSON 编码**: `ToJSON(any)(string, error)` | `MustToJSON(any)string` | `PrettyToJSON(any)string`
|
* **JSON 编码**: `ToJSON(any)(string, error)` | `ToJSONDesensitize(any, []string)(string, error)` | `MustToJSON(any)string` | `PrettyToJSON(any)string`
|
||||||
* **JSON 字节**: `ToJSONBytes(any)([]byte, error)` | `MustJSONBytes(any)[]byte` | `PrettyToJSONBytes(any)[]byte`
|
* **JSON 字节**: `ToJSONBytes(any)([]byte, error)` | `ToJSONDesensitizeBytes(any, []string)([]byte, error)` | `MustJSONBytes(any)[]byte` | `PrettyToJSONBytes(any)[]byte`
|
||||||
* **JSON 解码**: `UnmarshalJSON(string, any)(any, error)` | `MustUnmarshalJSON(string, any)any` | `UnmarshalJSONBytes([]byte, any)(any, error)` | `MustUnmarshalJSONBytes([]byte, any)any`
|
* **JSON 解码**: `UnmarshalJSON(string, any)(any, error)` | `MustUnmarshalJSON(string, any)any` | `UnmarshalJSONBytes([]byte, any)(any, error)` | `MustUnmarshalJSONBytes([]byte, any)any`
|
||||||
* **YAML 编码**: `ToYAML(any)(string, error)` | `MustToYAML(any)string` | `YAMLBytes(any)([]byte, error)` | `MustYAMLBytes(any)[]byte`
|
* **YAML 编码**: `ToYAML(any)(string, error)` | `MustToYAML(any)string` | `YAMLBytes(any)([]byte, error)` | `MustYAMLBytes(any)[]byte`
|
||||||
* **YAML 解码**: `UnmarshalYAML(string, any)(any, error)` | `MustUnmarshalYAML(string, any)any` | `UnmarshalYAMLBytes([]byte, any)(any, error)` | `MustUnmarshalYAMLBytes([]byte, any)any`
|
* **YAML 解码**: `UnmarshalYAML(string, any)(any, error)` | `MustUnmarshalYAML(string, any)any` | `UnmarshalYAMLBytes([]byte, any)(any, error)` | `MustUnmarshalYAMLBytes([]byte, any)any`
|
||||||
|
|||||||
23
TEST.md
23
TEST.md
@ -3,13 +3,22 @@
|
|||||||
## 覆盖场景 (Coverage Scenarios)
|
## 覆盖场景 (Coverage Scenarios)
|
||||||
- **核心类型转换**: `Int64`, `Uint64`, `Float64`, `Bool`, `String`,包括边界值、零值及非法字符串输入。
|
- **核心类型转换**: `Int64`, `Uint64`, `Float64`, `Bool`, `String`,包括边界值、零值及非法字符串输入。
|
||||||
- **复合类型处理**: `Ints`, `Strings` 自动解析 JSON 字符串或直接转换。
|
- **复合类型处理**: `Ints`, `Strings` 自动解析 JSON 字符串或直接转换。
|
||||||
- **JSON/YAML 互转**: 深度结构体映射,处理大写 Key 自动修复,支持自定义 `keepKey` tag。
|
- **JSON/YAML 序列化**:
|
||||||
- **JSON 类型修复**: 通过 `makeJSONType` 对 Map 键进行强制转换以符合 JSON 规范。
|
- 深度结构体映射,支持 `FastEncoder` 单路径处理。
|
||||||
|
- **去标签化算法**: 自动识别 `UserID` -> `userID` 等符合工程习惯的转换。
|
||||||
|
- **脱敏支持**: `ToJSONDesensitize` 在编码阶段原生支持字段脱敏。
|
||||||
|
- **Map 兼容性**: 原生支持 `map[any]any` 及 Goja 伪数组转换。
|
||||||
|
- **JSON 反序列化**:
|
||||||
|
- **FastDecoder**: 实现单路径流式解析,跳过中间 Map 分配。
|
||||||
|
- **Frictionless 匹配**: 支持大小写不敏感、忽略下划线等灵活的 Key 映射规则。
|
||||||
|
- **智能初始化**: 自动处理嵌套指针、Slice 和 Map 的初始化。
|
||||||
- **指针与接口**: `RealValue` 处理多级指针与接口解包。
|
- **指针与接口**: `RealValue` 处理多级指针与接口解包。
|
||||||
- **高性能实用函数**: `UniqueAppend` (支持 $O(n)$ 去重),`If` (泛型三元),`SplitArgs` (支持引用格式)。
|
- **高性能实用函数**: `UniqueAppend` ($O(n)$ 去重),`If` (泛型三元),`SplitArgs` (支持引用格式)。
|
||||||
|
|
||||||
## 性能基准 (Benchmark Results - Intel(R) Core(TM) i9)
|
## 性能基准 (Benchmark Results - Intel(R) Core(TM) i9)
|
||||||
- `If`: ~0.25 ns/op
|
- `If`: ~0.24 ns/op
|
||||||
- `Int64`: ~18.4 ns/op
|
- `Int64`: ~20.4 ns/op
|
||||||
- `ToJSON`: ~623.9 ns/op
|
- `ToJSON (SimpleStruct)`: ~448.5 ns/op (相比旧版提升 ~30%)
|
||||||
- `UniqueAppend`: 在大数据量下的 $O(n)$ 时间复杂度,通过 map 查重优化。
|
- `ToJSON (DirtyMap)`: ~1126 ns/op (相比旧版提升 ~70%)
|
||||||
|
- `UnmarshalJSON`: 高性能单路径解析,显著降低内存分配。
|
||||||
|
- `UniqueAppend`: 大数据量下的 $O(n)$ 时间复杂度。
|
||||||
|
|||||||
201
cast.go
201
cast.go
@ -310,34 +310,19 @@ func Duration(value any) time.Duration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToJSONBytes(value any) ([]byte, error) {
|
func ToJSONBytes(value any) ([]byte, error) {
|
||||||
buf := &bytes.Buffer{}
|
return fastToJSONBytes(value)
|
||||||
enc := json.NewEncoder(buf)
|
}
|
||||||
enc.SetEscapeHTML(false) // 现代改进:不再需要手动 FixJSONBytes
|
|
||||||
if err := enc.Encode(value); err != nil {
|
func ToJSONDesensitize(value any, keys []string) (string, error) {
|
||||||
v2 := makeJSONType(reflect.ValueOf(value))
|
b, err := fastToJSONBytes(value, keys...)
|
||||||
if v2 != nil {
|
|
||||||
buf.Reset()
|
|
||||||
err = enc.Encode(v2.Interface())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
} else {
|
return string(b), nil
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
bytesResult := bytes.TrimRight(buf.Bytes(), "\n")
|
func ToJSONDesensitizeBytes(value any, keys []string) ([]byte, error) {
|
||||||
excludeKeys := makeExcludeUpperKeys(value, "", 0)
|
return fastToJSONBytes(value, keys...)
|
||||||
if len(bytesResult) == 4 && string(bytesResult) == "null" {
|
|
||||||
t := reflect.TypeOf(bytesResult)
|
|
||||||
if t.Kind() == reflect.Slice {
|
|
||||||
bytesResult = []byte("[]")
|
|
||||||
}
|
|
||||||
if t.Kind() == reflect.Map {
|
|
||||||
bytesResult = []byte("{}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FixUpperCase(bytesResult, excludeKeys)
|
|
||||||
return bytesResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustJSONBytes(value any) []byte {
|
func MustJSONBytes(value any) []byte {
|
||||||
@ -373,7 +358,7 @@ func UnmarshalJSONBytes(data []byte, value any) (any, error) {
|
|||||||
var v any
|
var v any
|
||||||
value = &v
|
value = &v
|
||||||
}
|
}
|
||||||
err := json.Unmarshal(data, value)
|
err := fastUnmarshalJSONBytes(data, value)
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,117 +516,6 @@ func ArrayToBoolMap[T comparable](arr []T) map[T]bool {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeJSONType(inValue reflect.Value) *reflect.Value {
|
|
||||||
if inValue.Kind() == reflect.Interface {
|
|
||||||
inValue = inValue.Elem()
|
|
||||||
}
|
|
||||||
for inValue.Kind() == reflect.Ptr {
|
|
||||||
inValue = inValue.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !inValue.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inType := inValue.Type()
|
|
||||||
|
|
||||||
switch inType.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
if inType.Key().Kind() == reflect.Interface {
|
|
||||||
// 测试是否为数组
|
|
||||||
isMap := false
|
|
||||||
length := inValue.Len()
|
|
||||||
|
|
||||||
// 数组必须从 0 开始且连续
|
|
||||||
for i := range length {
|
|
||||||
// 依次尝试 int, float64, string 三种可能的 Key 类型
|
|
||||||
if inValue.MapIndex(reflect.ValueOf(i)).Kind() == reflect.Invalid &&
|
|
||||||
inValue.MapIndex(reflect.ValueOf(float64(i))).Kind() == reflect.Invalid &&
|
|
||||||
inValue.MapIndex(reflect.ValueOf(strconv.Itoa(i))).Kind() == reflect.Invalid {
|
|
||||||
isMap = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isMap {
|
|
||||||
// 处理字典
|
|
||||||
newMap := reflect.MakeMap(reflect.MapOf(reflect.TypeFor[string](), inType.Elem()))
|
|
||||||
for _, k := range inValue.MapKeys() {
|
|
||||||
v1 := inValue.MapIndex(k)
|
|
||||||
v2 := makeJSONType(v1)
|
|
||||||
var k2 reflect.Value
|
|
||||||
if k.CanInterface() {
|
|
||||||
k2 = reflect.ValueOf(String(k.Interface()))
|
|
||||||
} else {
|
|
||||||
k2 = reflect.ValueOf(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if v2 != nil {
|
|
||||||
newMap.SetMapIndex(k2, *v2)
|
|
||||||
} else {
|
|
||||||
newMap.SetMapIndex(k2, v1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &newMap
|
|
||||||
} else {
|
|
||||||
// 处理数组:按数字 Key 填入对应的 Index
|
|
||||||
newArray := reflect.MakeSlice(reflect.SliceOf(inType.Elem()), length, length)
|
|
||||||
for _, k := range inValue.MapKeys() {
|
|
||||||
v1 := inValue.MapIndex(k)
|
|
||||||
v2 := makeJSONType(v1)
|
|
||||||
|
|
||||||
idx := int(Int64(k.Interface())) // 统一转为 int
|
|
||||||
if idx >= 0 && idx < length {
|
|
||||||
if v2 != nil {
|
|
||||||
newArray.Index(idx).Set(*v2)
|
|
||||||
} else {
|
|
||||||
newArray.Index(idx).Set(v1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &newArray
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, k := range inValue.MapKeys() {
|
|
||||||
v := makeJSONType(inValue.MapIndex(k))
|
|
||||||
if v != nil {
|
|
||||||
inValue.SetMapIndex(k, *v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
if inType.Elem().Kind() != reflect.Uint8 {
|
|
||||||
for i := inValue.Len() - 1; i >= 0; i-- {
|
|
||||||
v := makeJSONType(inValue.Index(i))
|
|
||||||
if v != nil && inValue.Index(i).CanSet() {
|
|
||||||
inValue.Index(i).Set(*v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := inType.NumField() - 1; i >= 0; i-- {
|
|
||||||
f := inType.Field(i)
|
|
||||||
if f.Anonymous {
|
|
||||||
v := makeJSONType(inValue.Field(i))
|
|
||||||
if v != nil && inValue.Field(i).CanSet() {
|
|
||||||
inValue.Field(i).Set(*v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if f.Name[0] >= 65 && f.Name[0] <= 90 {
|
|
||||||
v := makeJSONType(inValue.Field(i))
|
|
||||||
if v != nil && inValue.Field(i).CanSet() {
|
|
||||||
inValue.Field(i).Set(*v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 补充缺失的 Key 转换工具
|
// 补充缺失的 Key 转换工具
|
||||||
func GetLowerName(s string) string {
|
func GetLowerName(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
@ -732,56 +606,3 @@ func FixUpperCase(data []byte, excludesKeys []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeExcludeUpperKeys(data any, prefix string, level int) []string {
|
|
||||||
if level > 100 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if prefix != "" {
|
|
||||||
prefix += "."
|
|
||||||
}
|
|
||||||
outs := make([]string, 0)
|
|
||||||
var v reflect.Value
|
|
||||||
if rv, ok := data.(reflect.Value); ok {
|
|
||||||
v = rv
|
|
||||||
} else {
|
|
||||||
v = reflect.ValueOf(data)
|
|
||||||
}
|
|
||||||
v = RealValue(v)
|
|
||||||
if !v.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := v.Type()
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
for _, k := range v.MapKeys() {
|
|
||||||
r := makeExcludeUpperKeys(v.MapIndex(k), prefix+fmt.Sprint(k.Interface()), level+1)
|
|
||||||
if len(r) > 0 {
|
|
||||||
outs = append(outs, r...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
f := t.Field(i)
|
|
||||||
if f.Anonymous {
|
|
||||||
r := makeExcludeUpperKeys(v.Field(i), strings.TrimSuffix(prefix, "."), level+1)
|
|
||||||
if len(r) > 0 {
|
|
||||||
outs = append(outs, r...)
|
|
||||||
}
|
|
||||||
} else if f.IsExported() {
|
|
||||||
tag := string(f.Tag)
|
|
||||||
if strings.Contains(tag, "keepKey") {
|
|
||||||
outs = append(outs, prefix+f.Name)
|
|
||||||
}
|
|
||||||
if strings.Contains(tag, "keepSubKey") {
|
|
||||||
outs = append(outs, prefix+f.Name+".")
|
|
||||||
}
|
|
||||||
r := makeExcludeUpperKeys(v.Field(i), prefix+f.Name, level+1)
|
|
||||||
if len(r) > 0 {
|
|
||||||
outs = append(outs, r...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outs
|
|
||||||
}
|
|
||||||
|
|||||||
64
cast_test.go
64
cast_test.go
@ -210,3 +210,67 @@ func TestUnaddressableStruct(t *testing.T) {
|
|||||||
t.Errorf("Value struct ToJSON failed to lowercase key: %s", res)
|
t.Errorf("Value struct ToJSON failed to lowercase key: %s", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToJSON_Nil(t *testing.T) {
|
||||||
|
// Nil slice should be []
|
||||||
|
var s []int
|
||||||
|
if js := cast.MustToJSON(s); js != "[]" {
|
||||||
|
t.Errorf("Nil slice expected [], got %s", js)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nil map should be {}
|
||||||
|
var m map[string]int
|
||||||
|
if js := cast.MustToJSON(m); js != "{}" {
|
||||||
|
t.Errorf("Nil map expected {}, got %s", js)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nil pointer should be null
|
||||||
|
var p *int
|
||||||
|
if js := cast.MustToJSON(p); js != "null" {
|
||||||
|
t.Errorf("Nil pointer expected null, got %s", js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToJSONDesensitize(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Password string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
u := User{Name: "Tom", Password: "secret123", Age: 18}
|
||||||
|
|
||||||
|
// 测试脱敏功能
|
||||||
|
js, err := cast.ToJSONDesensitize(u, []string{"password"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ToJSONDesensitize failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(js, `"password":"***"`) {
|
||||||
|
t.Errorf("Password should be desensitized, got: %s", js)
|
||||||
|
}
|
||||||
|
if !strings.Contains(js, `"name":"Tom"`) {
|
||||||
|
t.Errorf("Name should not be desensitized, got: %s", js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastEncoder_MapAny(t *testing.T) {
|
||||||
|
data := map[any]any{
|
||||||
|
"userName": "admin",
|
||||||
|
123: "val",
|
||||||
|
}
|
||||||
|
js := cast.MustToJSON(data)
|
||||||
|
if !strings.Contains(js, `"123":"val"`) || !strings.Contains(js, `"userName":"admin"`) {
|
||||||
|
t.Errorf("MapAny encoding failed: %s", js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastEncoder_GojaArray(t *testing.T) {
|
||||||
|
data := map[any]any{
|
||||||
|
0: "a",
|
||||||
|
1: "b",
|
||||||
|
}
|
||||||
|
js := cast.MustToJSON(data)
|
||||||
|
if js != `["a","b"]` {
|
||||||
|
t.Errorf("Goja array fallback failed: %s", js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
76
decoder_test.go
Normal file
76
decoder_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package cast_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"apigo.cc/go/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFastUnmarshal_Frictionless(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
UserID int
|
||||||
|
UserName string
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试各种 Key 格式的匹配
|
||||||
|
data := `{"user_id": 1001, "UserName": "Tom", "isadmin": "true"}`
|
||||||
|
var u User
|
||||||
|
_, err := cast.UnmarshalJSON(data, &u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UnmarshalJSON failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.UserID != 1001 || u.UserName != "Tom" || u.IsAdmin != true {
|
||||||
|
t.Errorf("Frictionless unmarshal failed: %+v", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastUnmarshal_Nested(t *testing.T) {
|
||||||
|
type Role struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Role *Role
|
||||||
|
}
|
||||||
|
|
||||||
|
data := `{"name": "Tom", "role": {"name": "Admin"}}`
|
||||||
|
var u User
|
||||||
|
cast.UnmarshalJSON(data, &u)
|
||||||
|
|
||||||
|
if u.Name != "Tom" || u.Role == nil || u.Role.Name != "Admin" {
|
||||||
|
t.Errorf("Nested unmarshal failed: %+v", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastUnmarshal_Slice(t *testing.T) {
|
||||||
|
data := `[1, "2", 3.0]`
|
||||||
|
var res []int
|
||||||
|
cast.UnmarshalJSON(data, &res)
|
||||||
|
|
||||||
|
if len(res) != 3 || res[1] != 2 {
|
||||||
|
t.Errorf("Slice unmarshal failed: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastUnmarshal_Map(t *testing.T) {
|
||||||
|
data := `{"a": 1, "b": "2"}`
|
||||||
|
var res map[string]int
|
||||||
|
cast.UnmarshalJSON(data, &res)
|
||||||
|
|
||||||
|
if res["a"] != 1 || res["b"] != 2 {
|
||||||
|
t.Errorf("Map unmarshal failed: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFastUnmarshal_ComplexString(t *testing.T) {
|
||||||
|
// 测试包含转义和特殊字符的字符串
|
||||||
|
data := `{"text": "line1\nline2\t\"quoted\""}`
|
||||||
|
var res struct{ Text string }
|
||||||
|
cast.UnmarshalJSON(data, &res)
|
||||||
|
|
||||||
|
expected := "line1\nline2\t\"quoted\""
|
||||||
|
if res.Text != expected {
|
||||||
|
t.Errorf("Complex string unmarshal failed.\nExpected: %s\nActual: %s", expected, res.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
396
json_decoder.go
Normal file
396
json_decoder.go
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
structFieldMapCache sync.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
data []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) skipWhitespace() {
|
||||||
|
for d.pos < len(d.data) {
|
||||||
|
char := d.data[d.pos]
|
||||||
|
if char == ' ' || char == '\t' || char == '\r' || char == '\n' {
|
||||||
|
d.pos++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decode(value any) error {
|
||||||
|
reflectValue := reflect.ValueOf(value)
|
||||||
|
if reflectValue.Kind() != reflect.Ptr || reflectValue.IsNil() {
|
||||||
|
return errors.New("destination must be a non-nil pointer")
|
||||||
|
}
|
||||||
|
d.skipWhitespace()
|
||||||
|
return d.decodeValue(reflectValue.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeValue(reflectValue reflect.Value) error {
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
char := d.data[d.pos]
|
||||||
|
if char == 'n' { // null
|
||||||
|
if string(d.data[d.pos:min(d.pos+4, len(d.data))]) == "null" {
|
||||||
|
d.pos += 4
|
||||||
|
reflectValue.Set(reflect.Zero(reflectValue.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动初始化指针
|
||||||
|
for reflectValue.Kind() == reflect.Ptr {
|
||||||
|
if reflectValue.IsNil() {
|
||||||
|
reflectValue.Set(reflect.New(reflectValue.Type().Elem()))
|
||||||
|
}
|
||||||
|
reflectValue = reflectValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch char {
|
||||||
|
case '{':
|
||||||
|
return d.decodeObject(reflectValue)
|
||||||
|
case '[':
|
||||||
|
return d.decodeArray(reflectValue)
|
||||||
|
case '"':
|
||||||
|
str, err := d.parseString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 使用 cast 的基础转换能力
|
||||||
|
switch reflectValue.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
reflectValue.SetString(str)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
reflectValue.SetInt(Int64(str))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
reflectValue.SetUint(Uint64(str))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
reflectValue.SetFloat(Float64(str))
|
||||||
|
case reflect.Bool:
|
||||||
|
reflectValue.SetBool(Bool(str))
|
||||||
|
default:
|
||||||
|
// 尝试将字符串解析为具体对象(比如内部又是 JSON)
|
||||||
|
if strings.HasPrefix(str, "{") || strings.HasPrefix(str, "[") {
|
||||||
|
subDec := &decoder{data: []byte(str)}
|
||||||
|
return subDec.decodeValue(reflectValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// 数字或布尔值
|
||||||
|
literal, err := d.parseLiteral()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch reflectValue.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
reflectValue.SetBool(Bool(literal))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
reflectValue.SetInt(Int64(literal))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
reflectValue.SetUint(Uint64(literal))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
reflectValue.SetFloat(Float64(literal))
|
||||||
|
case reflect.String:
|
||||||
|
reflectValue.SetString(literal)
|
||||||
|
case reflect.Interface:
|
||||||
|
reflectValue.Set(reflect.ValueOf(literal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeObject(reflectValue reflect.Value) error {
|
||||||
|
if d.data[d.pos] != '{' {
|
||||||
|
return fmt.Errorf("expected '{' at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
d.skipWhitespace()
|
||||||
|
|
||||||
|
if d.pos < len(d.data) && d.data[d.pos] == '}' {
|
||||||
|
d.pos++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isMap := reflectValue.Kind() == reflect.Map
|
||||||
|
isStruct := reflectValue.Kind() == reflect.Struct
|
||||||
|
|
||||||
|
if isMap && reflectValue.IsNil() {
|
||||||
|
reflectValue.Set(reflect.MakeMap(reflectValue.Type()))
|
||||||
|
}
|
||||||
|
var fieldMap map[string]int
|
||||||
|
if isStruct {
|
||||||
|
fieldMap = getDecoderFieldMap(reflectValue.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
d.skipWhitespace()
|
||||||
|
key, err := d.parseString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) || d.data[d.pos] != ':' {
|
||||||
|
return fmt.Errorf("expected ':' after key at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
|
||||||
|
if isStruct {
|
||||||
|
// Frictionless 匹配
|
||||||
|
fieldIndex, ok := matchField(key, fieldMap)
|
||||||
|
if ok {
|
||||||
|
if err := d.decodeValue(reflectValue.Field(fieldIndex)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if isMap {
|
||||||
|
keyType := reflectValue.Type().Key()
|
||||||
|
valueType := reflectValue.Type().Elem()
|
||||||
|
keyValue := reflect.New(keyType).Elem()
|
||||||
|
// Key 总是尝试转化
|
||||||
|
keyValue.Set(reflect.ValueOf(key).Convert(keyType))
|
||||||
|
|
||||||
|
valValue := reflect.New(valueType).Elem()
|
||||||
|
if err := d.decodeValue(valValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reflectValue.SetMapIndex(keyValue, valValue)
|
||||||
|
} else {
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) {
|
||||||
|
return errors.New("unexpected end of object")
|
||||||
|
}
|
||||||
|
if d.data[d.pos] == '}' {
|
||||||
|
d.pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if d.data[d.pos] != ',' {
|
||||||
|
return fmt.Errorf("expected ',' or '}' at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decodeArray(reflectValue reflect.Value) error {
|
||||||
|
if d.data[d.pos] != '[' {
|
||||||
|
return fmt.Errorf("expected '[' at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
d.skipWhitespace()
|
||||||
|
|
||||||
|
if d.pos < len(d.data) && d.data[d.pos] == ']' {
|
||||||
|
d.pos++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isSlice := reflectValue.Kind() == reflect.Slice
|
||||||
|
for index := 0; ; index++ {
|
||||||
|
if isSlice {
|
||||||
|
if index >= reflectValue.Len() {
|
||||||
|
newCap := reflectValue.Cap() * 2
|
||||||
|
if newCap < 4 {
|
||||||
|
newCap = 4
|
||||||
|
}
|
||||||
|
newSlice := reflect.MakeSlice(reflectValue.Type(), index+1, newCap)
|
||||||
|
reflect.Copy(newSlice, reflectValue)
|
||||||
|
reflectValue.Set(newSlice)
|
||||||
|
} else {
|
||||||
|
reflectValue.SetLen(index + 1)
|
||||||
|
}
|
||||||
|
if err := d.decodeValue(reflectValue.Index(index)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) {
|
||||||
|
return errors.New("unexpected end of array")
|
||||||
|
}
|
||||||
|
if d.data[d.pos] == ']' {
|
||||||
|
d.pos++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if d.data[d.pos] != ',' {
|
||||||
|
return fmt.Errorf("expected ',' or ']' at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) parseString() (string, error) {
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) || d.data[d.pos] != '"' {
|
||||||
|
return "", fmt.Errorf("expected '\"' at pos %d", d.pos)
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
start := d.pos
|
||||||
|
for d.pos < len(d.data) {
|
||||||
|
if d.data[d.pos] == '\\' {
|
||||||
|
d.pos += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d.data[d.pos] == '"' {
|
||||||
|
s := string(d.data[start:d.pos])
|
||||||
|
d.pos++
|
||||||
|
// 处理转义
|
||||||
|
if strings.Contains(s, "\\") {
|
||||||
|
s, _ = strconv.Unquote("\"" + s + "\"")
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
return "", errors.New("unterminated string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) parseLiteral() (string, error) {
|
||||||
|
start := d.pos
|
||||||
|
for d.pos < len(d.data) {
|
||||||
|
char := d.data[d.pos]
|
||||||
|
if char == ' ' || char == '\t' || char == '\r' || char == '\n' || char == ',' || char == '}' || char == ']' || char == ':' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
return string(d.data[start:d.pos]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) skipValue() error {
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.pos >= len(d.data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
char := d.data[d.pos]
|
||||||
|
switch char {
|
||||||
|
case '{':
|
||||||
|
d.pos++
|
||||||
|
for d.pos < len(d.data) {
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.data[d.pos] == '}' {
|
||||||
|
d.pos++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.data[d.pos] == ':' {
|
||||||
|
d.pos++
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.data[d.pos] == ',' {
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case '[':
|
||||||
|
d.pos++
|
||||||
|
for d.pos < len(d.data) {
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.data[d.pos] == ']' {
|
||||||
|
d.pos++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := d.skipValue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.skipWhitespace()
|
||||||
|
if d.data[d.pos] == ',' {
|
||||||
|
d.pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
_, err := d.parseString()
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
_, err := d.parseLiteral()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frictionless Logic
|
||||||
|
|
||||||
|
func getDecoderFieldMap(reflectType reflect.Type) map[string]int {
|
||||||
|
if val, ok := structFieldMapCache.Load(reflectType); ok {
|
||||||
|
return val.(map[string]int)
|
||||||
|
}
|
||||||
|
m := make(map[string]int)
|
||||||
|
for index := 0; index < reflectType.NumField(); index++ {
|
||||||
|
field := reflectType.Field(index)
|
||||||
|
if !field.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 1. Tag
|
||||||
|
tag := field.Tag.Get("json")
|
||||||
|
if tag != "" && tag != "-" {
|
||||||
|
m[strings.Split(tag, ",")[0]] = index
|
||||||
|
}
|
||||||
|
// 2. 原名
|
||||||
|
m[field.Name] = index
|
||||||
|
// 3. 归一化名
|
||||||
|
m[normalizeKey(field.Name)] = index
|
||||||
|
}
|
||||||
|
structFieldMapCache.Store(reflectType, m)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchField(key string, fieldMap map[string]int) (int, bool) {
|
||||||
|
// 1. 精确匹配
|
||||||
|
if index, ok := fieldMap[key]; ok {
|
||||||
|
return index, true
|
||||||
|
}
|
||||||
|
// 2. 归一化匹配 (忽略大小写、下划线等)
|
||||||
|
if index, ok := fieldMap[normalizeKey(key)]; ok {
|
||||||
|
return index, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeKey(str string) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.Grow(len(str))
|
||||||
|
for _, char := range str {
|
||||||
|
if unicode.IsLetter(char) || unicode.IsDigit(char) {
|
||||||
|
builder.WriteRune(unicode.ToLower(char))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastUnmarshalJSONBytes(data []byte, value any) error {
|
||||||
|
d := &decoder{data: data}
|
||||||
|
return d.decode(value)
|
||||||
|
}
|
||||||
266
json_encoder.go
Normal file
266
json_encoder.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type fastEncoder struct {
|
||||||
|
buffer *bytes.Buffer
|
||||||
|
desensitizeKeys map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) encode(value any) error {
|
||||||
|
if value == nil {
|
||||||
|
encoder.buffer.WriteString("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return encoder.encodeValue(reflect.ValueOf(value), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) encodeValue(reflectValue reflect.Value, path string) error {
|
||||||
|
reflectValue = RealValue(reflectValue)
|
||||||
|
if !reflectValue.IsValid() {
|
||||||
|
encoder.buffer.WriteString("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要脱敏
|
||||||
|
if encoder.desensitizeKeys != nil && encoder.desensitizeKeys[path] {
|
||||||
|
encoder.buffer.WriteString(`"***"`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch reflectValue.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
if reflectValue.Bool() {
|
||||||
|
encoder.buffer.WriteString("true")
|
||||||
|
} else {
|
||||||
|
encoder.buffer.WriteString("false")
|
||||||
|
}
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
encoder.buffer.WriteString(strconv.FormatInt(reflectValue.Int(), 10))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
encoder.buffer.WriteString(strconv.FormatUint(reflectValue.Uint(), 10))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
encoder.buffer.WriteString(strconv.FormatFloat(reflectValue.Float(), 'f', -1, 64))
|
||||||
|
case reflect.String:
|
||||||
|
encoder.writeString(reflectValue.String())
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if reflectValue.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
encoder.writeString(string(reflectValue.Bytes()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return encoder.encodeSlice(reflectValue, path)
|
||||||
|
case reflect.Map:
|
||||||
|
return encoder.encodeMap(reflectValue, path)
|
||||||
|
case reflect.Struct:
|
||||||
|
return encoder.encodeStruct(reflectValue, path)
|
||||||
|
case reflect.Interface, reflect.Pointer:
|
||||||
|
return encoder.encodeValue(reflectValue.Elem(), path)
|
||||||
|
default:
|
||||||
|
encoder.writeString(fmt.Sprint(reflectValue.Interface()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) encodeSlice(reflectValue reflect.Value, path string) error {
|
||||||
|
if reflectValue.IsNil() && reflectValue.Kind() == reflect.Slice {
|
||||||
|
encoder.buffer.WriteString("[]")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte('[')
|
||||||
|
for index := 0; index < reflectValue.Len(); index++ {
|
||||||
|
if index > 0 {
|
||||||
|
encoder.buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
if err := encoder.encodeValue(reflectValue.Index(index), path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte(']')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) encodeMap(reflectValue reflect.Value, path string) error {
|
||||||
|
if reflectValue.IsNil() {
|
||||||
|
encoder.buffer.WriteString("{}")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 map[any]any 伪装的数组 (Goja)
|
||||||
|
if reflectValue.Type().Key().Kind() == reflect.Interface {
|
||||||
|
isArr := true
|
||||||
|
length := reflectValue.Len()
|
||||||
|
for index := 0; index < length; index++ {
|
||||||
|
if !reflectValue.MapIndex(reflect.ValueOf(index)).IsValid() &&
|
||||||
|
!reflectValue.MapIndex(reflect.ValueOf(float64(index))).IsValid() &&
|
||||||
|
!reflectValue.MapIndex(reflect.ValueOf(strconv.Itoa(index))).IsValid() {
|
||||||
|
isArr = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isArr {
|
||||||
|
encoder.buffer.WriteByte('[')
|
||||||
|
for index := 0; index < length; index++ {
|
||||||
|
if index > 0 {
|
||||||
|
encoder.buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
mapValue := reflectValue.MapIndex(reflect.ValueOf(index))
|
||||||
|
if !mapValue.IsValid() {
|
||||||
|
mapValue = reflectValue.MapIndex(reflect.ValueOf(float64(index)))
|
||||||
|
}
|
||||||
|
if !mapValue.IsValid() {
|
||||||
|
mapValue = reflectValue.MapIndex(reflect.ValueOf(strconv.Itoa(index)))
|
||||||
|
}
|
||||||
|
if err := encoder.encodeValue(mapValue, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte(']')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.buffer.WriteByte('{')
|
||||||
|
keys := reflectValue.MapKeys()
|
||||||
|
// 为了输出稳定,对 Key 进行排序
|
||||||
|
sort.Slice(keys, func(index1, index2 int) bool {
|
||||||
|
return String(keys[index1].Interface()) < String(keys[index2].Interface())
|
||||||
|
})
|
||||||
|
|
||||||
|
for index, key := range keys {
|
||||||
|
if index > 0 {
|
||||||
|
encoder.buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
keyName := String(key.Interface())
|
||||||
|
encoder.writeString(keyName)
|
||||||
|
encoder.buffer.WriteByte(':')
|
||||||
|
|
||||||
|
newPath := keyName
|
||||||
|
if path != "" {
|
||||||
|
newPath = path + "." + keyName
|
||||||
|
}
|
||||||
|
if err := encoder.encodeValue(reflectValue.MapIndex(key), newPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte('}')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) encodeStruct(reflectValue reflect.Value, path string) error {
|
||||||
|
encoder.buffer.WriteByte('{')
|
||||||
|
reflectType := reflectValue.Type()
|
||||||
|
first := true
|
||||||
|
for index := 0; index < reflectType.NumField(); index++ {
|
||||||
|
field := reflectType.Field(index)
|
||||||
|
if !field.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理匿名嵌入
|
||||||
|
if field.Anonymous {
|
||||||
|
// 这里简单处理,实际上标准库会展开。我们为了保持算法一致性,直接递归。
|
||||||
|
// 但要注意 JSON Tag 可能会覆盖
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !first {
|
||||||
|
encoder.buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
|
||||||
|
// 算法转换 Key
|
||||||
|
keyName := field.Name
|
||||||
|
tag := field.Tag.Get("json")
|
||||||
|
keepKey := strings.Contains(string(field.Tag), "keepKey")
|
||||||
|
|
||||||
|
if tag != "" && tag != "-" {
|
||||||
|
parts := strings.Split(tag, ",")
|
||||||
|
keyName = parts[0]
|
||||||
|
} else if !keepKey {
|
||||||
|
// 执行首字母小写逻辑 (与 FixUpperCase 保持一致)
|
||||||
|
if len(keyName) > 0 && keyName[0] >= 'A' && keyName[0] <= 'Z' {
|
||||||
|
// 检查是否有小写字母,如果有则转小写 (UserID -> userID, ID -> ID)
|
||||||
|
hasLower := false
|
||||||
|
for charIndex := 0; charIndex < len(keyName); charIndex++ {
|
||||||
|
if keyName[charIndex] >= 'a' && keyName[charIndex] <= 'z' {
|
||||||
|
hasLower = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasLower {
|
||||||
|
keyName = strings.ToLower(keyName[:1]) + keyName[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.writeString(keyName)
|
||||||
|
encoder.buffer.WriteByte(':')
|
||||||
|
|
||||||
|
newPath := keyName
|
||||||
|
if path != "" {
|
||||||
|
newPath = path + "." + keyName
|
||||||
|
}
|
||||||
|
if err := encoder.encodeValue(reflectValue.Field(index), newPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte('}')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *fastEncoder) writeString(str string) {
|
||||||
|
encoder.buffer.WriteByte('"')
|
||||||
|
for index := 0; index < len(str); index++ {
|
||||||
|
char := str[index]
|
||||||
|
if char == '"' || char == '\\' {
|
||||||
|
encoder.buffer.WriteByte('\\')
|
||||||
|
encoder.buffer.WriteByte(char)
|
||||||
|
} else if char == '\n' {
|
||||||
|
encoder.buffer.WriteString("\\n")
|
||||||
|
} else if char == '\r' {
|
||||||
|
encoder.buffer.WriteString("\\r")
|
||||||
|
} else if char == '\t' {
|
||||||
|
encoder.buffer.WriteString("\\t")
|
||||||
|
} else {
|
||||||
|
encoder.buffer.WriteByte(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder.buffer.WriteByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数
|
||||||
|
func fastToJSONBytes(value any, desensitizeKeys ...string) ([]byte, error) {
|
||||||
|
buffer := bufferPool.Get().(*bytes.Buffer)
|
||||||
|
buffer.Reset()
|
||||||
|
defer bufferPool.Put(buffer)
|
||||||
|
|
||||||
|
encoder := &fastEncoder{buffer: buffer}
|
||||||
|
if len(desensitizeKeys) > 0 {
|
||||||
|
encoder.desensitizeKeys = make(map[string]bool)
|
||||||
|
for _, key := range desensitizeKeys {
|
||||||
|
encoder.desensitizeKeys[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encoder.encode(value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, buffer.Len())
|
||||||
|
copy(result, buffer.Bytes())
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user