refactor(cast): v1.0.4 性能优化与命名规范对齐
- 重构 UniqueAppend 实现 - 优化核心解析函数逻辑 - 提升字符处理性能 (由 AI 维护)
This commit is contained in:
parent
f54de7942c
commit
211ed95856
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
go.sum
|
||||||
45
AI.md
45
AI.md
@ -1,45 +0,0 @@
|
|||||||
# AI Coding Context: @go/cast
|
|
||||||
|
|
||||||
本索引供 AI 模型理解 `@go/cast` 的逻辑与调用原则,以生成符合本项目设计哲学的代码。
|
|
||||||
|
|
||||||
## 🤖 AI 行为准则
|
|
||||||
|
|
||||||
1. **优先使用 cast 进行转换**:在处理类型不确定的数据时,优先使用 `cast.Int()`, `cast.String()` 等,避免手动类型断言。
|
|
||||||
2. **禁止在 Struct 中冗余添加 JSON Tag**:除非有特殊重命名需求,否则默认使用大写导出,告知用户使用 `cast.FixedJson()` 来自动实现小写 Key 序列化。
|
|
||||||
3. **使用 If 替代简单 if-else 赋值**:在需要进行条件赋值时,优先使用泛型函数 `cast.If(cond, val1, val2)`。
|
|
||||||
4. **无错误处理倾向**:理解 `cast` 函数不返回 `error`,在生成调用代码时不要进行错误检查。
|
|
||||||
|
|
||||||
## 🛠 API Reference
|
|
||||||
|
|
||||||
### 基础转换
|
|
||||||
- `func Int(v any) int` / `func Int64(v any) int64` / `func Uint(v any) uint` / `func Uint64(v any) uint64`
|
|
||||||
- `func Float(v any) float32` / `func Float64(v any) float64`
|
|
||||||
- `func String(v any) string` / `func StringP(v any) string`
|
|
||||||
- `func Bool(v any) bool`
|
|
||||||
- `func Ints(v any) []int64` / `func Strings(v any) []string`
|
|
||||||
- `func Duration(v string) time.Duration`
|
|
||||||
|
|
||||||
### 泛型工具
|
|
||||||
- `func If[T any](cond bool, a, b T) T`:泛型三元表达式。
|
|
||||||
- `func Switch[T any](i uint, args ...T) T`:泛型分支选择。
|
|
||||||
- `func In[T comparable](arr []T, val T) bool`:判断切片是否包含某值。
|
|
||||||
|
|
||||||
### 序列化 (JSON/YAML)
|
|
||||||
- `func Json(v any) string` / `func JsonP(v any) string` / `func JsonBytes(v any) []byte`
|
|
||||||
- `func FixedJson(v any) string`:Struct 首字母转小写。
|
|
||||||
- `func UnJson(str string, v any) any` / `func UnJsonBytes(data []byte, v any) any`
|
|
||||||
- `func Yaml(v any) string` / `func UnYaml(str string, v any) any`
|
|
||||||
|
|
||||||
### 辅助工具
|
|
||||||
- `func StringPtr(v string) *string` / `func IntPtr(v int) *int` / `func Int64Ptr(v int64) *int64` / `func BoolPtr(v bool) *bool`
|
|
||||||
- `func SplitTrim(s, sep string) []string` / `func SplitArgs(s string) []string`
|
|
||||||
- `func GetLowerName(s string) string` / `func GetUpperName(s string) string`
|
|
||||||
|
|
||||||
## 🧩 典型模式 (Best Practices)
|
|
||||||
|
|
||||||
* **✅ 推荐**:
|
|
||||||
```go
|
|
||||||
status := cast.If(ok, "A", "B")
|
|
||||||
age := cast.Int("18")
|
|
||||||
jsonStr := cast.Json(myStruct)
|
|
||||||
```
|
|
||||||
@ -11,20 +11,34 @@ import (
|
|||||||
|
|
||||||
func TestEdgeCases(t *testing.T) {
|
func TestEdgeCases(t *testing.T) {
|
||||||
// 1. 极端数值
|
// 1. 极端数值
|
||||||
if cast.Int64(math.MaxInt64) != math.MaxInt64 { t.Error("MaxInt64 failed") }
|
if cast.Int64(math.MaxInt64) != math.MaxInt64 {
|
||||||
if cast.Float64("1.7976931348623157e+308") == 0 { t.Error("MaxFloat64 string failed") }
|
t.Error("MaxInt64 failed")
|
||||||
|
}
|
||||||
|
if cast.Float64("1.7976931348623157e+308") == 0 {
|
||||||
|
t.Error("MaxFloat64 string failed")
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 各种非法格式字符串
|
// 2. 各种非法格式字符串
|
||||||
if cast.Int("abc") != 0 { t.Error("Invalid string to int should be 0") }
|
if cast.Int("abc") != 0 {
|
||||||
if cast.Float("1.2.3.4") != 0 { t.Error("Invalid float string should be 0") }
|
t.Error("Invalid string to int should be 0")
|
||||||
if cast.Bool("not_a_bool") != false { t.Error("Invalid bool string should be false") }
|
}
|
||||||
|
if cast.Float("1.2.3.4") != 0 {
|
||||||
|
t.Error("Invalid float string should be 0")
|
||||||
|
}
|
||||||
|
if cast.Bool("not_a_bool") != false {
|
||||||
|
t.Error("Invalid bool string should be false")
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 深度嵌套与空指针
|
// 3. 深度嵌套与空指针
|
||||||
var p ***int
|
var p ***int
|
||||||
if cast.Int(p) != 0 { t.Error("Deep nil pointer to int failed") }
|
if cast.Int(p) != 0 {
|
||||||
|
t.Error("Deep nil pointer to int failed")
|
||||||
|
}
|
||||||
|
|
||||||
// 4. JSON 异常输入
|
// 4. JSON 异常输入
|
||||||
if cast.UnJson("{invalid_json}", nil) == nil { t.Log("UnJson handled invalid input") }
|
if _, err := cast.UnJson("{invalid_json}", nil); err == nil {
|
||||||
|
t.Log("UnJson handled invalid input")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 性能测试 (Performance / Benchmarks) ---
|
// --- 性能测试 (Performance / Benchmarks) ---
|
||||||
@ -57,7 +71,7 @@ func BenchmarkJson(b *testing.B) {
|
|||||||
}
|
}
|
||||||
u := User{ID: 1, Name: "Benchmark User"}
|
u := User{ID: 1, Name: "Benchmark User"}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = cast.Json(u)
|
_ = cast.MustToJson(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +81,66 @@ func BenchmarkIf(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkSplitTrim(b *testing.B) {
|
func BenchmarkSplit(b *testing.B) {
|
||||||
s := " a, b, c, d, e, f, g "
|
s := " a, b, c, d, e, f, g "
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = cast.SplitTrim(s, ",")
|
_ = cast.Split(s, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 测试 Int 转换的快速路径 (命中 switch case)
|
||||||
|
func BenchmarkInt_FastPath(b *testing.B) {
|
||||||
|
val := "123456"
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = cast.Int(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 测试 Int 转换的慢速路径 (退化为反射)
|
||||||
|
type customInt int
|
||||||
|
|
||||||
|
func BenchmarkInt_SlowPath(b *testing.B) {
|
||||||
|
var val customInt = 123456
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// 自定义类型无法命中 switch 的基础类型,会走 reflect.ValueOf
|
||||||
|
_ = cast.Int(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 测试 String 转换的快速路径
|
||||||
|
func BenchmarkString_FastPath(b *testing.B) {
|
||||||
|
val := 123456.789
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = cast.String(val) // 命中 strconv.FormatFloat,极快
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 测试 ToJson 性能 (对比自定义逻辑与标准库)
|
||||||
|
func BenchmarkToJson_SimpleStruct(b *testing.B) {
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
u := User{ID: 1, Name: "Benchmark User"}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// 这里包含了你特有的 makeJsonType 清洗逻辑和 FixUpperCase 逻辑
|
||||||
|
_ = cast.MustToJson(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 测试 Goja Map 的清洗性能
|
||||||
|
func BenchmarkToJson_DirtyMap(b *testing.B) {
|
||||||
|
// 模拟恶劣数据:包含 any key 的 map
|
||||||
|
val := map[any]any{
|
||||||
|
0: "A",
|
||||||
|
1: map[any]any{"key": "B"},
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = cast.MustToJson(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
166
cast_test.go
166
cast_test.go
@ -3,22 +3,64 @@ package cast_test
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"apigo.cc/go/cast"
|
"apigo.cc/go/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerics(t *testing.T) {
|
func TestGenerics(t *testing.T) {
|
||||||
// Test If
|
// Test If
|
||||||
if cast.If(true, 1, 2) != 1 { t.Error("If int failed") }
|
if cast.If(true, 1, 2) != 1 {
|
||||||
if cast.If(false, "A", "B") != "B" { t.Error("If string failed") }
|
t.Error("If int failed")
|
||||||
|
}
|
||||||
|
if cast.If(false, "A", "B") != "B" {
|
||||||
|
t.Error("If string failed")
|
||||||
|
}
|
||||||
|
|
||||||
// Test In
|
// Test In
|
||||||
if !cast.In([]int{1, 2, 3}, 2) { t.Error("In int failed") }
|
if !cast.In([]int{1, 2, 3}, 2) {
|
||||||
if cast.In([]string{"A", "B"}, "C") { t.Error("In string negative failed") }
|
t.Error("In int failed")
|
||||||
|
}
|
||||||
|
if cast.In([]string{"A", "B"}, "C") {
|
||||||
|
t.Error("In string negative failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test Switch
|
func TestStructToJson(t *testing.T) {
|
||||||
if cast.Switch(1, "A", "B", "C") != "B" { t.Error("Switch failed") }
|
type User struct {
|
||||||
if cast.Switch(5, 1, 2) != 0 { t.Error("Switch out of range failed") }
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
u := User{Name: "Tom", Age: 18}
|
||||||
|
js := cast.MustToJson(u)
|
||||||
|
// 验证首字母小写逻辑
|
||||||
|
if !strings.Contains(js, `"name":"Tom"`) || !strings.Contains(js, `"age":18`) {
|
||||||
|
t.Errorf("Struct to JSON auto-lowercase failed: %s", js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJsonToStruct(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
data := `{"Name":"Tom","age":18}`
|
||||||
|
var u User
|
||||||
|
cast.UnJson(data, &u)
|
||||||
|
if u.Name != "Tom" || u.Age != 18 {
|
||||||
|
t.Error("UnJson to struct failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYamlConversion(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
c := Config{Port: 8080}
|
||||||
|
yamlStr := cast.MustToYaml(c)
|
||||||
|
if !strings.Contains(yamlStr, "port: 8080") {
|
||||||
|
t.Errorf("Yaml conversion failed: %s", yamlStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecialJson(t *testing.T) {
|
func TestSpecialJson(t *testing.T) {
|
||||||
@ -30,14 +72,14 @@ func TestSpecialJson(t *testing.T) {
|
|||||||
|
|
||||||
// 标准 json.Marshal 会变成 "<a> \u0026 <b>"
|
// 标准 json.Marshal 会变成 "<a> \u0026 <b>"
|
||||||
// 我们期望输出原始字符 "<a> & <b>"
|
// 我们期望输出原始字符 "<a> & <b>"
|
||||||
js := cast.Json(c)
|
js := cast.MustToJson(c)
|
||||||
expected := `{"text":"<a> & <b>"}`
|
expected := `{"text":"<a> & <b>"}`
|
||||||
if js != expected {
|
if js != expected {
|
||||||
t.Errorf("Special JSON failed.\nExpected: %s\nActual: %s", expected, js)
|
t.Errorf("Special JSON failed.\nExpected: %s\nActual: %s", expected, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 漂亮打印测试
|
// 漂亮打印测试
|
||||||
jsP := cast.JsonP(c)
|
jsP := cast.PrettyToJson(c)
|
||||||
if !strings.Contains(jsP, "&") || !strings.Contains(jsP, "\n") {
|
if !strings.Contains(jsP, "&") || !strings.Contains(jsP, "\n") {
|
||||||
t.Error("JsonP special content failed")
|
t.Error("JsonP special content failed")
|
||||||
}
|
}
|
||||||
@ -53,7 +95,7 @@ func TestComplexJsonType(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
js := cast.Json(data)
|
js := cast.MustToJson(data)
|
||||||
// 期望 123 被转为 "123" 且内容正确
|
// 期望 123 被转为 "123" 且内容正确
|
||||||
if !strings.Contains(js, `"123":"numeric key"`) || !strings.Contains(js, `"ok":true`) {
|
if !strings.Contains(js, `"123":"numeric key"`) || !strings.Contains(js, `"ok":true`) {
|
||||||
t.Errorf("Complex JSON type failed: %s", js)
|
t.Errorf("Complex JSON type failed: %s", js)
|
||||||
@ -62,23 +104,109 @@ func TestComplexJsonType(t *testing.T) {
|
|||||||
|
|
||||||
func TestPointerHelpers(t *testing.T) {
|
func TestPointerHelpers(t *testing.T) {
|
||||||
s := "hello"
|
s := "hello"
|
||||||
if *cast.StringPtr(s) != s { t.Error("StringPtr failed") }
|
if *cast.Ptr(s) != s {
|
||||||
|
t.Error("String Ptr failed")
|
||||||
|
}
|
||||||
|
|
||||||
i := 100
|
i := 100
|
||||||
if *cast.IntPtr(i) != i { t.Error("IntPtr failed") }
|
if *cast.Ptr(i) != i {
|
||||||
|
t.Error("Int Ptr failed")
|
||||||
|
}
|
||||||
|
|
||||||
if *cast.BoolPtr(true) != true || *cast.BoolPtr(false) != false {
|
if *cast.Ptr(true) != true || *cast.Ptr(false) != false {
|
||||||
t.Error("BoolPtr failed")
|
t.Error("Bool Ptr failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNameConversions(t *testing.T) {
|
func TestNameConversions(t *testing.T) {
|
||||||
if cast.GetLowerName("UserName") != "userName" { t.Error("GetLowerName failed") }
|
if cast.GetLowerName("UserName") != "userName" {
|
||||||
if cast.GetUpperName("userName") != "UserName" { t.Error("GetUpperName failed") }
|
t.Error("GetLowerName failed")
|
||||||
|
}
|
||||||
|
if cast.GetUpperName("userName") != "UserName" {
|
||||||
|
t.Error("GetUpperName failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyValues(t *testing.T) {
|
func TestEmptyValues(t *testing.T) {
|
||||||
if cast.Int(nil) != 0 { t.Error("Int(nil) failed") }
|
if cast.Int(nil) != 0 {
|
||||||
if cast.String(nil) != "" { t.Error("String(nil) failed") }
|
t.Error("Int(nil) failed")
|
||||||
if cast.Bool(nil) != false { t.Error("Bool(nil) failed") }
|
}
|
||||||
|
if cast.String(nil) != "" {
|
||||||
|
t.Error("String(nil) failed")
|
||||||
|
}
|
||||||
|
if cast.Bool(nil) != false {
|
||||||
|
t.Error("Bool(nil) failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:专门测试 Goja 引擎等导致数组变成 Map[any]any 的补丁逻辑
|
||||||
|
func TestGojaMapToArrayFallback(t *testing.T) {
|
||||||
|
// 模拟由于 JS 引擎导致的 map[interface{}]interface{} 伪装数组
|
||||||
|
// key 为 float64 或 int
|
||||||
|
mockGojaArray := map[interface{}]interface{}{
|
||||||
|
float64(0): "apple",
|
||||||
|
int(1): "banana",
|
||||||
|
"2": "cherry", // 字符串形式的数字也应该被正确转换
|
||||||
|
}
|
||||||
|
|
||||||
|
js := cast.MustToJson(mockGojaArray)
|
||||||
|
|
||||||
|
// 我们期望它被正确识别并转化为标准的 JSON 数组,且顺序不会乱
|
||||||
|
expected := `["apple","banana","cherry"]`
|
||||||
|
if js != expected {
|
||||||
|
t.Errorf("Goja Map-to-Array fallback failed.\nExpected: %s\nActual: %s", expected, js)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:测试基础类型的转换与默认值
|
||||||
|
func TestTypeCasting(t *testing.T) {
|
||||||
|
// 测试多种输入向 Int 转换
|
||||||
|
if cast.Int(12.34) != 12 {
|
||||||
|
t.Error("Float to Int failed")
|
||||||
|
}
|
||||||
|
if cast.Int("100") != 100 {
|
||||||
|
t.Error("String to Int failed")
|
||||||
|
}
|
||||||
|
if cast.Int([]byte("200")) != 200 {
|
||||||
|
t.Error("Bytes to Int failed")
|
||||||
|
}
|
||||||
|
if cast.Int(true) != 1 {
|
||||||
|
t.Error("Bool to Int failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试多种输入向 Bool 转换
|
||||||
|
if !cast.Bool("T") || !cast.Bool("true") || !cast.Bool(1) || !cast.Bool(-1) {
|
||||||
|
t.Error("Truthy to Bool failed")
|
||||||
|
}
|
||||||
|
if cast.Bool("false") || cast.Bool(0) || cast.Bool(nil) {
|
||||||
|
t.Error("Falsy to Bool failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试 Duration
|
||||||
|
if cast.Duration("1s") != time.Second {
|
||||||
|
t.Error("String to Duration failed")
|
||||||
|
}
|
||||||
|
if cast.Duration(2000) != 2000 {
|
||||||
|
t.Error("Int to Duration failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:测试结构体不可寻址时的安全性 (防止 CanSet panic)
|
||||||
|
func TestUnaddressableStruct(t *testing.T) {
|
||||||
|
type Dummy struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
d := Dummy{Name: "Test"}
|
||||||
|
|
||||||
|
// 传入值类型而不是指针。如果 makeJsonType 中遗漏了 CanSet,这里会直接 panic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("ToJson with value struct panicked! Missing CanSet() check: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
res := cast.MustToJson(d)
|
||||||
|
if !strings.Contains(res, "\"name\"") {
|
||||||
|
t.Errorf("Value struct ToJson failed to lowercase key: %s", res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user