2026-04-22 01:52:30 +08:00
|
|
|
|
package cast_test
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"testing"
|
2026-05-01 00:11:40 +08:00
|
|
|
|
"time"
|
2026-04-22 01:52:30 +08:00
|
|
|
|
|
|
|
|
|
|
"apigo.cc/go/cast"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func TestGenerics(t *testing.T) {
|
|
|
|
|
|
// Test If
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if cast.If(true, 1, 2) != 1 {
|
|
|
|
|
|
t.Error("If int failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
if cast.If(false, "A", "B") != "B" {
|
|
|
|
|
|
t.Error("If string failed")
|
|
|
|
|
|
}
|
2026-04-22 01:52:30 +08:00
|
|
|
|
|
|
|
|
|
|
// Test In
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if !cast.In([]int{1, 2, 3}, 2) {
|
|
|
|
|
|
t.Error("In int failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
if cast.In([]string{"A", "B"}, "C") {
|
|
|
|
|
|
t.Error("In string negative failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 00:41:23 +08:00
|
|
|
|
func TestStructToJSON(t *testing.T) {
|
2026-05-01 00:11:40 +08:00
|
|
|
|
type User struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Age int
|
|
|
|
|
|
}
|
|
|
|
|
|
u := User{Name: "Tom", Age: 18}
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](u)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
// 验证首字母小写逻辑
|
|
|
|
|
|
if !strings.Contains(js, `"name":"Tom"`) || !strings.Contains(js, `"age":18`) {
|
|
|
|
|
|
t.Errorf("Struct to JSON auto-lowercase failed: %s", js)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 00:41:23 +08:00
|
|
|
|
func TestJSONToStruct(t *testing.T) {
|
2026-05-01 00:11:40 +08:00
|
|
|
|
type User struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Age int
|
|
|
|
|
|
}
|
|
|
|
|
|
data := `{"Name":"Tom","age":18}`
|
|
|
|
|
|
var u User
|
2026-05-01 00:41:23 +08:00
|
|
|
|
cast.UnmarshalJSON(data, &u)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if u.Name != "Tom" || u.Age != 18 {
|
2026-05-01 00:41:23 +08:00
|
|
|
|
t.Error("UnmarshalJSON to struct failed")
|
2026-05-01 00:11:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-22 01:52:30 +08:00
|
|
|
|
|
2026-05-01 00:41:23 +08:00
|
|
|
|
func TestSpecialJSON(t *testing.T) {
|
2026-04-22 01:52:30 +08:00
|
|
|
|
// 关键测试:特殊 HTML 字符序列化不应被转义
|
|
|
|
|
|
type Content struct {
|
|
|
|
|
|
Text string `json:"text"`
|
|
|
|
|
|
}
|
|
|
|
|
|
c := Content{Text: "<a> & <b>"}
|
2026-05-01 00:11:40 +08:00
|
|
|
|
|
2026-04-22 01:52:30 +08:00
|
|
|
|
// 标准 json.Marshal 会变成 "<a> \u0026 <b>"
|
|
|
|
|
|
// 我们期望输出原始字符 "<a> & <b>"
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](c)
|
2026-04-22 01:52:30 +08:00
|
|
|
|
expected := `{"text":"<a> & <b>"}`
|
|
|
|
|
|
if js != expected {
|
|
|
|
|
|
t.Errorf("Special JSON failed.\nExpected: %s\nActual: %s", expected, js)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 漂亮打印测试
|
2026-05-01 00:41:23 +08:00
|
|
|
|
jsP := cast.PrettyToJSON(c)
|
2026-04-22 01:52:30 +08:00
|
|
|
|
if !strings.Contains(jsP, "&") || !strings.Contains(jsP, "\n") {
|
2026-05-01 00:41:23 +08:00
|
|
|
|
t.Error("JSONP special content failed")
|
2026-04-22 01:52:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 00:41:23 +08:00
|
|
|
|
func TestComplexJSONType(t *testing.T) {
|
2026-04-22 01:52:30 +08:00
|
|
|
|
// 测试 map[interface{}]interface{} 这种标准库无法处理的类型
|
|
|
|
|
|
data := map[interface{}]interface{}{
|
|
|
|
|
|
"name": "Tom",
|
|
|
|
|
|
123: "numeric key",
|
|
|
|
|
|
"sub": map[interface{}]interface{}{
|
|
|
|
|
|
"ok": true,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
2026-05-01 00:11:40 +08:00
|
|
|
|
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](data)
|
2026-04-22 01:52:30 +08:00
|
|
|
|
// 期望 123 被转为 "123" 且内容正确
|
|
|
|
|
|
if !strings.Contains(js, `"123":"numeric key"`) || !strings.Contains(js, `"ok":true`) {
|
|
|
|
|
|
t.Errorf("Complex JSON type failed: %s", js)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestPointerHelpers(t *testing.T) {
|
|
|
|
|
|
s := "hello"
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if *cast.Ptr(s) != s {
|
|
|
|
|
|
t.Error("String Ptr failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-22 01:52:30 +08:00
|
|
|
|
i := 100
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if *cast.Ptr(i) != i {
|
|
|
|
|
|
t.Error("Int Ptr failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if *cast.Ptr(true) != true || *cast.Ptr(false) != false {
|
|
|
|
|
|
t.Error("Bool Ptr failed")
|
2026-04-22 01:52:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestNameConversions(t *testing.T) {
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if cast.GetLowerName("UserName") != "userName" {
|
|
|
|
|
|
t.Error("GetLowerName failed")
|
|
|
|
|
|
}
|
|
|
|
|
|
if cast.GetUpperName("userName") != "UserName" {
|
|
|
|
|
|
t.Error("GetUpperName failed")
|
|
|
|
|
|
}
|
2026-04-22 01:52:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestEmptyValues(t *testing.T) {
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if cast.Int(nil) != 0 {
|
|
|
|
|
|
t.Error("Int(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", // 字符串形式的数字也应该被正确转换
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](mockGojaArray)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 我们期望它被正确识别并转化为标准的 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"}
|
|
|
|
|
|
|
2026-05-01 00:41:23 +08:00
|
|
|
|
// 传入值类型而不是指针。如果 makeJSONType 中遗漏了 CanSet,这里会直接 panic
|
2026-05-01 00:11:40 +08:00
|
|
|
|
defer func() {
|
|
|
|
|
|
if r := recover(); r != nil {
|
2026-05-01 00:41:23 +08:00
|
|
|
|
t.Errorf("ToJSON with value struct panicked! Missing CanSet() check: %v", r)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
2026-05-04 10:52:15 +08:00
|
|
|
|
res := cast.As[string](d)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
if !strings.Contains(res, "\"name\"") {
|
2026-05-01 00:41:23 +08:00
|
|
|
|
t.Errorf("Value struct ToJSON failed to lowercase key: %s", res)
|
2026-05-01 00:11:40 +08:00
|
|
|
|
}
|
2026-04-22 01:52:30 +08:00
|
|
|
|
}
|
2026-05-02 23:00:44 +08:00
|
|
|
|
|
|
|
|
|
|
func TestToJSON_Nil(t *testing.T) {
|
|
|
|
|
|
// Nil slice should be []
|
|
|
|
|
|
var s []int
|
2026-05-04 10:52:15 +08:00
|
|
|
|
if js := cast.As[string](s); js != "[]" {
|
2026-05-02 23:00:44 +08:00
|
|
|
|
t.Errorf("Nil slice expected [], got %s", js)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Nil map should be {}
|
|
|
|
|
|
var m map[string]int
|
2026-05-04 10:52:15 +08:00
|
|
|
|
if js := cast.As[string](m); js != "{}" {
|
2026-05-02 23:00:44 +08:00
|
|
|
|
t.Errorf("Nil map expected {}, got %s", js)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Nil pointer should be null
|
|
|
|
|
|
var p *int
|
2026-05-04 10:52:15 +08:00
|
|
|
|
if js := cast.As[string](p); js != "null" {
|
2026-05-02 23:00:44 +08:00
|
|
|
|
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",
|
|
|
|
|
|
}
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](data)
|
2026-05-02 23:00:44 +08:00
|
|
|
|
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",
|
|
|
|
|
|
}
|
2026-05-04 10:52:15 +08:00
|
|
|
|
js := cast.As[string](data)
|
2026-05-02 23:00:44 +08:00
|
|
|
|
if js != `["a","b"]` {
|
|
|
|
|
|
t.Errorf("Goja array fallback failed: %s", js)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|