refactor: achieve zero-friction API (v1.2.0) - To[T] without error, rename Quiet to As, remove redundant APIs (by AI)
This commit is contained in:
parent
837c7cf0d7
commit
684a33e0e5
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
## [v1.2.0] - 2026-05-04
|
## [v1.2.0] - 2026-05-04
|
||||||
### Added
|
### Added
|
||||||
- **语义化 API**: 引入 `To[T]` (严格/含错) 和 `As[T]` (静默/零值) 泛型接口,作为全能类型转换入口。
|
- **零摩擦 API**: 核心 API `To[T]`, `ToJSON`, `FromJSON`, `ToMap`, `ToSlice` 全部重构为**不返回错误**,遇错静默返回零值。
|
||||||
- **摩擦消除工具**: 新增 `Quiet[T]` 和 `Must[T]` 通用辅助函数,用于包装 error-returning 函数。
|
- **As 包装器**: 原 `Quiet` 更名为 `As`,用于一键消除传统 `(value, error)` 函数的摩擦。
|
||||||
- **智能穿透**: `To[T]` 支持 JSON 自动双向转换(Input string/[]byte <-> Target struct/map/slice)。
|
|
||||||
### Removed
|
### Removed
|
||||||
- **YAML 支持**: 移除 `gopkg.in/yaml.v3` 依赖及所有 YAML 相关 API。
|
- **YAML 支持**: 移除 `gopkg.in/yaml.v3` 依赖。
|
||||||
- **API 整合**: 移除专门的 `AsXxx` 和 `MustXxx` 系列函数(如 `AsMap`, `AsJSON`, `MustToJSON` 等),推荐使用 `As[T]` 或 `Quiet/Must` 包装。
|
- **Must/As[T] 系列**: 移除 `Must` 及所有特殊的 `AsXxx` 系列函数,统一通过 `To[T]` 或 `As` 包装实现。
|
||||||
|
- **冗余 API**: 移除 `PrettyToJSONBytes` 和 `ToJSONDesensitize`,保留对应的 `PrettyToJSON` 和 `ToJSONDesensitizeBytes`。
|
||||||
|
|
||||||
## [v1.1.2] - 2026-05-04
|
## [v1.1.2] - 2026-05-04
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
61
README.md
61
README.md
@ -5,10 +5,11 @@
|
|||||||
|
|
||||||
## 🎯 设计哲学
|
## 🎯 设计哲学
|
||||||
|
|
||||||
`@go/cast` 是一个为“敏捷开发”设计的 Go 基础工具库。其核心目标是**消除类型摩擦**,让开发者在处理数据时更关注“我想要什么”而不是“原本是什么”。
|
`@go/cast` 是一个为“极致敏捷”设计的 Go 基础工具库。其核心目标是**彻底消除摩擦**:
|
||||||
|
|
||||||
* **语义化 API**:通过 `To[T]` (严格/含错) 配合 `Quiet` (静默) 或 `Must` (断言) 提供一致的调用体验。
|
* **零错误返回**:核心 API 不返回 `error`,在失败或非法转换时静默返回类型零值。
|
||||||
* **智能穿透**:自动识别 JSON 文本、结构体映射、KV 展开等复杂场景。
|
* **语义化 As 包装**:提供 `As` 函数用于将传统“值+错误”双返回结果一键转化为单值。
|
||||||
|
* **智能自动穿透**:万能 `To[T]` 自动识别 JSON 文本、复杂容器映射、指针穿透等场景。
|
||||||
* **极致性能**:内置 FastEncoder/FastDecoder,单路径处理,最小化内存分配。
|
* **极致性能**:内置 FastEncoder/FastDecoder,单路径处理,最小化内存分配。
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
@ -22,55 +23,51 @@ go get apigo.cc/go/cast
|
|||||||
```go
|
```go
|
||||||
import "apigo.cc/go/cast"
|
import "apigo.cc/go/cast"
|
||||||
|
|
||||||
// 1. 语义化转换 (To[T] 返回错误)
|
// 1. 万能转换 (To[T] 永不报错,遇错返回零值)
|
||||||
val, err := cast.To[int]("123") // 123, nil
|
age := cast.To[int]("18") // 18
|
||||||
|
uid := cast.To[int]("abc") // 0 (静默失败)
|
||||||
|
|
||||||
// 2. 摩擦消除工具 (Quiet 返回零值, Must 失败 Panic)
|
// 2. 传统函数消除摩擦 (As 将双返回包装为单值)
|
||||||
age := cast.Quiet(cast.To[int]("abc")) // 0
|
b := cast.As(json.Marshal(data)) // 返回 []byte,忽略 error
|
||||||
uid := cast.Must(cast.To[int]("100")) // 100
|
|
||||||
|
|
||||||
// 3. 快捷方式 (cast.As[T] 等价于 cast.Quiet(cast.To[T]))
|
// 3. 智能 JSON 自动转换
|
||||||
name := cast.As[string](123) // "123"
|
|
||||||
|
|
||||||
// 4. 智能 JSON 自动转换
|
|
||||||
// 输入 map/struct -> 目标 string: 自动序列化
|
// 输入 map/struct -> 目标 string: 自动序列化
|
||||||
js := cast.As[string](map[string]int{"age": 18}) // `{"age":18}`
|
js := cast.To[string](map[string]int{"age": 18}) // `{"age":18}`
|
||||||
|
|
||||||
// 输入 string/[]byte -> 目标 struct/map: 自动反序列化
|
// 输入 string/[]byte -> 目标 struct/map: 自动反序列化
|
||||||
user, _ := cast.To[User](`{"name":"Tom"}`)
|
user := cast.To[User](`{"name":"Tom"}`)
|
||||||
|
|
||||||
// 5. 复杂容器转换 (Map/Slice)
|
// 4. 复杂容器转换 (Map/Slice)
|
||||||
m, _ := cast.ToMap[string, int]([]any{"id", "1", "score", 100})
|
m := cast.ToMap[string, int]([]any{"id", "1", "score", 100})
|
||||||
list := cast.As[[]int]([]string{"1", "2", "3"})
|
list := cast.To[[]int]([]string{"1", "2", "3"})
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠 API 指南
|
## 🛠 API 指南
|
||||||
|
|
||||||
### 核心语义 API
|
### 核心 API
|
||||||
|
|
||||||
1. **通用转换**
|
1. **通用转换**
|
||||||
* `To[T any](any) (T, error)` —— 万能转换入口。支持基础类型、Slice、Map 以及 JSON 的双向自动转换。
|
* `To[T any](any) T` —— 万能转换入口。支持基础类型、Slice、Map 以及 JSON 的双向自动转换。失败时返回类型零值。
|
||||||
* `As[T any](any) T` —— 零摩擦转换快捷方式。失败时返回类型零值。
|
* `As[T any](v T, err error) T` —— 错误消除工具。将传统的 `(value, error)` 返回值包装为单值。
|
||||||
|
|
||||||
2. **错误处理工具**
|
2. **容器转换**
|
||||||
* `Quiet[T any](T, error) T` —— 忽略错误,返回转换后的值(若有错则返回零值)。
|
* `ToMap[K, V](any) map[K]V` —— 构建新 Map。
|
||||||
* `Must[T any](T, error) T` —— 严谨模式。若有错误则直接 Panic,常用于全局初始化。
|
* `ToSlice[T](any) []T` —— 构建新 Slice。
|
||||||
|
|
||||||
3. **容器转换**
|
|
||||||
* `ToMap[K, V](any) (map[K]V, error)` —— 构建新 Map。
|
|
||||||
* `ToSlice[T](any) ([]T, error)` —— 构建新 Slice。
|
|
||||||
* `FillMap(target, source)` | `FillSlice(target, source)` —— 填充现有容器。
|
* `FillMap(target, source)` | `FillSlice(target, source)` —— 填充现有容器。
|
||||||
|
|
||||||
4. **JSON 序列化与构建**
|
3. **JSON 序列化与构建**
|
||||||
* **编码**: `ToJSON(any) (string, error)` | `ToJSONBytes(any) ([]byte, error)`
|
* **编码**: `ToJSON(any) string` | `ToJSONBytes(any) []byte`
|
||||||
* **解码**: `UnmarshalJSON(data, target) error` | `FromJSON[T](any) (T, error)`
|
* **解码**: `UnmarshalJSON(data, target) error` (底层 API,保留 error 用于调试) | `FromJSON[T](any) T`
|
||||||
* **进阶**: `ToJSONDesensitize(any, []string) (string, error)` (支持字段脱敏)
|
* **进阶**: `ToJSONDesensitizeBytes(any, []string) []byte` (支持字段脱敏) | `PrettyToJSON(any) string`
|
||||||
|
|
||||||
5. **泛型工具**
|
4. **泛型工具**
|
||||||
* `If[T any](bool, T, T) T` —— 三元逻辑。
|
* `If[T any](bool, T, T) T` —— 三元逻辑。
|
||||||
* `In[T comparable]([]T, T) bool` —— 包含判断。
|
* `In[T comparable]([]T, T) bool` —— 包含判断。
|
||||||
* `Ptr[T any](T) *T` —— 快速取指针。
|
* `Ptr[T any](T) *T` —— 快速取指针。
|
||||||
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速构建索引 Map。
|
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速构建索引 Map。
|
||||||
|
|
||||||
|
5. **基础转换 (直接调用,极致性能)**
|
||||||
|
* `Int`, `Int64`, `Uint`, `Uint64`, `Float`, `Float64`, `String`, `Bool`, `Duration`
|
||||||
|
|
||||||
## 🧪 验证状态
|
## 🧪 验证状态
|
||||||
测试全部通过,性能达标。详见:[TEST.md](./TEST.md)
|
测试全部通过,性能达标。详见:[TEST.md](./TEST.md)
|
||||||
|
|||||||
@ -71,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.As[string](u)
|
_ = cast.To[string](u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ func BenchmarkToJSON_SimpleStruct(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// 这里包含了你特有的 makeJSONType 清洗逻辑和 FixUpperCase 逻辑
|
// 这里包含了你特有的 makeJSONType 清洗逻辑和 FixUpperCase 逻辑
|
||||||
_ = cast.As[string](u)
|
_ = cast.To[string](u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +160,6 @@ func BenchmarkToJSON_DirtyMap(b *testing.B) {
|
|||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = cast.As[string](val)
|
_ = cast.To[string](val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
90
cast.go
90
cast.go
@ -25,8 +25,8 @@ func In[T comparable](arr []T, val T) bool {
|
|||||||
return slices.Contains(arr, val)
|
return slices.Contains(arr, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quiet 忽略错误,返回零值
|
// As 忽略错误,返回零值 (消除摩擦)
|
||||||
func Quiet[T any](v T, err error) T {
|
func As[T any](v T, err error) T {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var zero T
|
var zero T
|
||||||
return zero
|
return zero
|
||||||
@ -34,14 +34,6 @@ func Quiet[T any](v T, err error) T {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must 严谨模式,报错即刻崩溃
|
|
||||||
func Must[T any](v T, err error) T {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func RealValue(v reflect.Value) reflect.Value {
|
func RealValue(v reflect.Value) reflect.Value {
|
||||||
for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface {
|
for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
@ -53,8 +45,8 @@ func RealValue(v reflect.Value) reflect.Value {
|
|||||||
}
|
}
|
||||||
// --- Core Cast Logic ---
|
// --- Core Cast Logic ---
|
||||||
|
|
||||||
// To 泛型转换 (支持基础类型、Slice、Map 及 JSON 自动转换)
|
// To 泛型转换 (支持基础类型、Slice、Map 及 JSON 自动转换,零摩擦模式)
|
||||||
func To[T any](v any) (T, error) {
|
func To[T any](v any) T {
|
||||||
var zero T
|
var zero T
|
||||||
targetType := reflect.TypeOf((*T)(nil)).Elem()
|
targetType := reflect.TypeOf((*T)(nil)).Elem()
|
||||||
|
|
||||||
@ -65,11 +57,8 @@ func To[T any](v any) (T, error) {
|
|||||||
|
|
||||||
// 2. 处理 JSON 自动转换 (Input: struct/map/slice, Target: string/[]byte)
|
// 2. 处理 JSON 自动转换 (Input: struct/map/slice, Target: string/[]byte)
|
||||||
if isComplexValue(v) && (targetType.Kind() == reflect.String || (targetType.Kind() == reflect.Slice && targetType.Elem().Kind() == reflect.Uint8)) {
|
if isComplexValue(v) && (targetType.Kind() == reflect.String || (targetType.Kind() == reflect.Slice && targetType.Elem().Kind() == reflect.Uint8)) {
|
||||||
s, err := ToJSON(v)
|
s := ToJSON(v)
|
||||||
if err != nil {
|
return any(reflectCast(s, targetType).Interface()).(T)
|
||||||
return zero, err
|
|
||||||
}
|
|
||||||
return any(reflectCast(s, targetType).Interface()).(T), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 处理 Slice/Map
|
// 3. 处理 Slice/Map
|
||||||
@ -78,20 +67,20 @@ func To[T any](v any) (T, error) {
|
|||||||
ptr := reflect.New(targetType)
|
ptr := reflect.New(targetType)
|
||||||
ptr.Elem().Set(sv)
|
ptr.Elem().Set(sv)
|
||||||
fillToSlice(ptr.Elem(), v)
|
fillToSlice(ptr.Elem(), v)
|
||||||
return ptr.Elem().Interface().(T), nil
|
return ptr.Elem().Interface().(T)
|
||||||
}
|
}
|
||||||
if targetType.Kind() == reflect.Map {
|
if targetType.Kind() == reflect.Map {
|
||||||
mv := reflect.MakeMap(targetType)
|
mv := reflect.MakeMap(targetType)
|
||||||
fillToMap(mv, v)
|
fillToMap(mv, v)
|
||||||
return mv.Interface().(T), nil
|
return mv.Interface().(T)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 处理基础类型
|
// 4. 处理基础类型
|
||||||
res, err := reflectCastE(v, targetType)
|
res := reflectCast(v, targetType)
|
||||||
if err != nil {
|
if !res.IsValid() {
|
||||||
return zero, err
|
return zero
|
||||||
}
|
}
|
||||||
return res.Interface().(T), nil
|
return res.Interface().(T)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reflectCastE(value any, t reflect.Type) (reflect.Value, error) {
|
func reflectCastE(value any, t reflect.Type) (reflect.Value, error) {
|
||||||
@ -190,11 +179,6 @@ func ToFloat64E(v any) (float64, error) {
|
|||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// As 泛型转换 (失败返回零值)
|
|
||||||
func As[T any](v any) T {
|
|
||||||
return Quiet(To[T](v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isJSONText(v any) bool {
|
func isJSONText(v any) bool {
|
||||||
switch val := v.(type) {
|
switch val := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
@ -448,41 +432,27 @@ func Duration(value any) time.Duration {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToJSONBytes(value any) ([]byte, error) {
|
func ToJSONBytes(value any) []byte {
|
||||||
return fastToJSONBytes(value)
|
return As(fastToJSONBytes(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToJSONDesensitize(value any, keys []string) (string, error) {
|
func ToJSONDesensitizeBytes(value any, keys []string) []byte {
|
||||||
b, err := fastToJSONBytes(value, keys...)
|
return As(fastToJSONBytes(value, keys...))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(b), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToJSONDesensitizeBytes(value any, keys []string) ([]byte, error) {
|
func ToJSON(value any) string {
|
||||||
return fastToJSONBytes(value, keys...)
|
return string(ToJSONBytes(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToJSON(value any) (string, error) {
|
func PrettyToJSON(value any) string {
|
||||||
j, err := ToJSONBytes(value)
|
j := ToJSONBytes(value)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(j), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrettyToJSONBytes(value any) []byte {
|
|
||||||
j := Quiet(ToJSONBytes(value))
|
|
||||||
r := &bytes.Buffer{}
|
r := &bytes.Buffer{}
|
||||||
if err := json.Indent(r, j, "", " "); err == nil {
|
if err := json.Indent(r, j, "", " "); err == nil {
|
||||||
return r.Bytes()
|
return r.String()
|
||||||
}
|
}
|
||||||
return j
|
return string(j)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrettyToJSON(value any) string { return string(PrettyToJSONBytes(value)) }
|
|
||||||
|
|
||||||
func toBytes(data any) []byte {
|
func toBytes(data any) []byte {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -504,10 +474,10 @@ func UnmarshalJSON(data any, value any) error {
|
|||||||
return fastUnmarshalJSONBytes(b, value)
|
return fastUnmarshalJSONBytes(b, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromJSON[T any](data any) (T, error) {
|
func FromJSON[T any](data any) T {
|
||||||
var v T
|
var v T
|
||||||
err := UnmarshalJSON(data, &v)
|
_ = UnmarshalJSON(data, &v)
|
||||||
return v, err
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Others (Keep logic but clean style) ---
|
// --- Others (Keep logic but clean style) ---
|
||||||
@ -608,17 +578,17 @@ func ArrayToBoolMap[T comparable](arr []T) map[T]bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToMap 泛型构建新 Map
|
// ToMap 泛型构建新 Map
|
||||||
func ToMap[K comparable, V any](source any) (map[K]V, error) {
|
func ToMap[K comparable, V any](source any) map[K]V {
|
||||||
m := make(map[K]V)
|
m := make(map[K]V)
|
||||||
fillToMap(reflect.ValueOf(m), source)
|
fillToMap(reflect.ValueOf(m), source)
|
||||||
return m, nil
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSlice 泛型构建新 Slice
|
// ToSlice 泛型构建新 Slice
|
||||||
func ToSlice[T any](source any) ([]T, error) {
|
func ToSlice[T any](source any) []T {
|
||||||
var s []T
|
var s []T
|
||||||
fillToSlice(reflect.ValueOf(&s).Elem(), source)
|
fillToSlice(reflect.ValueOf(&s).Elem(), source)
|
||||||
return s, nil
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillMap 将 source 填充到目标 map 中 (兼容旧 API 逻辑)
|
// FillMap 将 source 填充到目标 map 中 (兼容旧 API 逻辑)
|
||||||
@ -727,7 +697,7 @@ func fillToSlice(sliceRv reflect.Value, source any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reflectCast(value any, t reflect.Type) reflect.Value {
|
func reflectCast(value any, t reflect.Type) reflect.Value {
|
||||||
return Quiet(reflectCastE(value, t))
|
return As(reflectCastE(value, t))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 补充缺失的 Key 转换工具
|
// 补充缺失的 Key 转换工具
|
||||||
|
|||||||
28
cast_test.go
28
cast_test.go
@ -32,7 +32,7 @@ func TestStructToJSON(t *testing.T) {
|
|||||||
Age int
|
Age int
|
||||||
}
|
}
|
||||||
u := User{Name: "Tom", Age: 18}
|
u := User{Name: "Tom", Age: 18}
|
||||||
js := cast.As[string](u)
|
js := cast.To[string](u)
|
||||||
// 验证首字母小写逻辑
|
// 验证首字母小写逻辑
|
||||||
if !strings.Contains(js, `"name":"Tom"`) || !strings.Contains(js, `"age":18`) {
|
if !strings.Contains(js, `"name":"Tom"`) || !strings.Contains(js, `"age":18`) {
|
||||||
t.Errorf("Struct to JSON auto-lowercase failed: %s", js)
|
t.Errorf("Struct to JSON auto-lowercase failed: %s", js)
|
||||||
@ -61,7 +61,7 @@ func TestSpecialJSON(t *testing.T) {
|
|||||||
|
|
||||||
// 标准 json.Marshal 会变成 "<a> \u0026 <b>"
|
// 标准 json.Marshal 会变成 "<a> \u0026 <b>"
|
||||||
// 我们期望输出原始字符 "<a> & <b>"
|
// 我们期望输出原始字符 "<a> & <b>"
|
||||||
js := cast.As[string](c)
|
js := cast.To[string](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)
|
||||||
@ -84,7 +84,7 @@ func TestComplexJSONType(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
js := cast.As[string](data)
|
js := cast.To[string](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)
|
||||||
@ -138,7 +138,7 @@ func TestGojaMapToArrayFallback(t *testing.T) {
|
|||||||
"2": "cherry", // 字符串形式的数字也应该被正确转换
|
"2": "cherry", // 字符串形式的数字也应该被正确转换
|
||||||
}
|
}
|
||||||
|
|
||||||
js := cast.As[string](mockGojaArray)
|
js := cast.To[string](mockGojaArray)
|
||||||
|
|
||||||
// 我们期望它被正确识别并转化为标准的 JSON 数组,且顺序不会乱
|
// 我们期望它被正确识别并转化为标准的 JSON 数组,且顺序不会乱
|
||||||
expected := `["apple","banana","cherry"]`
|
expected := `["apple","banana","cherry"]`
|
||||||
@ -194,7 +194,7 @@ func TestUnaddressableStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
res := cast.As[string](d)
|
res := cast.To[string](d)
|
||||||
if !strings.Contains(res, "\"name\"") {
|
if !strings.Contains(res, "\"name\"") {
|
||||||
t.Errorf("Value struct ToJSON failed to lowercase key: %s", res)
|
t.Errorf("Value struct ToJSON failed to lowercase key: %s", res)
|
||||||
}
|
}
|
||||||
@ -203,24 +203,24 @@ func TestUnaddressableStruct(t *testing.T) {
|
|||||||
func TestToJSON_Nil(t *testing.T) {
|
func TestToJSON_Nil(t *testing.T) {
|
||||||
// Nil slice should be []
|
// Nil slice should be []
|
||||||
var s []int
|
var s []int
|
||||||
if js := cast.As[string](s); js != "[]" {
|
if js := cast.To[string](s); js != "[]" {
|
||||||
t.Errorf("Nil slice expected [], got %s", js)
|
t.Errorf("Nil slice expected [], got %s", js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nil map should be {}
|
// Nil map should be {}
|
||||||
var m map[string]int
|
var m map[string]int
|
||||||
if js := cast.As[string](m); js != "{}" {
|
if js := cast.To[string](m); js != "{}" {
|
||||||
t.Errorf("Nil map expected {}, got %s", js)
|
t.Errorf("Nil map expected {}, got %s", js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nil pointer should be null
|
// Nil pointer should be null
|
||||||
var p *int
|
var p *int
|
||||||
if js := cast.As[string](p); js != "null" {
|
if js := cast.To[string](p); js != "null" {
|
||||||
t.Errorf("Nil pointer expected null, got %s", js)
|
t.Errorf("Nil pointer expected null, got %s", js)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToJSONDesensitize(t *testing.T) {
|
func TestToJSONDesensitizeBytes(t *testing.T) {
|
||||||
type User struct {
|
type User struct {
|
||||||
Name string
|
Name string
|
||||||
Password string
|
Password string
|
||||||
@ -229,10 +229,8 @@ func TestToJSONDesensitize(t *testing.T) {
|
|||||||
u := User{Name: "Tom", Password: "secret123", Age: 18}
|
u := User{Name: "Tom", Password: "secret123", Age: 18}
|
||||||
|
|
||||||
// 测试脱敏功能
|
// 测试脱敏功能
|
||||||
js, err := cast.ToJSONDesensitize(u, []string{"password"})
|
b := cast.ToJSONDesensitizeBytes(u, []string{"password"})
|
||||||
if err != nil {
|
js := string(b)
|
||||||
t.Fatalf("ToJSONDesensitize failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(js, `"password":"***"`) {
|
if !strings.Contains(js, `"password":"***"`) {
|
||||||
t.Errorf("Password should be desensitized, got: %s", js)
|
t.Errorf("Password should be desensitized, got: %s", js)
|
||||||
@ -247,7 +245,7 @@ func TestFastEncoder_MapAny(t *testing.T) {
|
|||||||
"userName": "admin",
|
"userName": "admin",
|
||||||
123: "val",
|
123: "val",
|
||||||
}
|
}
|
||||||
js := cast.As[string](data)
|
js := cast.To[string](data)
|
||||||
if !strings.Contains(js, `"123":"val"`) || !strings.Contains(js, `"userName":"admin"`) {
|
if !strings.Contains(js, `"123":"val"`) || !strings.Contains(js, `"userName":"admin"`) {
|
||||||
t.Errorf("MapAny encoding failed: %s", js)
|
t.Errorf("MapAny encoding failed: %s", js)
|
||||||
}
|
}
|
||||||
@ -258,7 +256,7 @@ func TestFastEncoder_GojaArray(t *testing.T) {
|
|||||||
0: "a",
|
0: "a",
|
||||||
1: "b",
|
1: "b",
|
||||||
}
|
}
|
||||||
js := cast.As[string](data)
|
js := cast.To[string](data)
|
||||||
if js != `["a","b"]` {
|
if js != `["a","b"]` {
|
||||||
t.Errorf("Goja array fallback failed: %s", js)
|
t.Errorf("Goja array fallback failed: %s", js)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func TestToMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test slice-to-map (KV)
|
// Test slice-to-map (KV)
|
||||||
m2 := cast.As[map[int]string]([]any{"1", "a", 2, 200})
|
m2 := cast.ToMap[int, string]([]any{"1", "a", 2, 200})
|
||||||
if m2[1] != "a" || m2[2] != "200" {
|
if m2[1] != "a" || m2[2] != "200" {
|
||||||
t.Errorf("Slice-to-map failed: %v", m2)
|
t.Errorf("Slice-to-map failed: %v", m2)
|
||||||
}
|
}
|
||||||
@ -58,27 +58,27 @@ func TestJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test FromJSON
|
// Test FromJSON
|
||||||
c2, err := cast.FromJSON[Config]([]byte(data))
|
c2 := cast.FromJSON[Config]([]byte(data))
|
||||||
if err != nil || c2.Port != 8080 {
|
if c2.Port != 8080 {
|
||||||
t.Errorf("FromJSON failed: %v, %v", err, c2)
|
t.Errorf("FromJSON failed: %v", c2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test As[T] from JSON
|
// Test To[T] from JSON
|
||||||
c3 := cast.As[Config](data)
|
c3 := cast.To[Config](data)
|
||||||
if c3.Port != 8080 {
|
if c3.Port != 8080 {
|
||||||
t.Errorf("As[Config] failed: %v", c3)
|
t.Errorf("To[Config] failed: %v", c3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenericAsSlice(t *testing.T) {
|
func TestGenericToSlice(t *testing.T) {
|
||||||
// Test As with various inputs
|
// Test To with various inputs
|
||||||
s1 := cast.As[[]int]("123")
|
s1 := cast.To[[]int]("123")
|
||||||
if len(s1) != 1 || s1[0] != 123 {
|
if len(s1) != 1 || s1[0] != 123 {
|
||||||
t.Errorf("As[[]int] primitive failed: %v", s1)
|
t.Errorf("To[[]int] primitive failed: %v", s1)
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := cast.As[[]string]([]int{1, 2})
|
s2 := cast.To[[]string]([]int{1, 2})
|
||||||
if len(s2) != 2 || s2[0] != "1" || s2[1] != "2" {
|
if len(s2) != 2 || s2[0] != "1" || s2[1] != "2" {
|
||||||
t.Errorf("As[[]string] slice failed: %v", s2)
|
t.Errorf("To[[]string] slice failed: %v", s2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,28 +5,25 @@ import (
|
|||||||
"apigo.cc/go/cast"
|
"apigo.cc/go/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToAs(t *testing.T) {
|
func TestTo(t *testing.T) {
|
||||||
// Basic types
|
// Basic types
|
||||||
if v, err := cast.To[int]("123"); err != nil || v != 123 {
|
if v := cast.To[int]("123"); v != 123 {
|
||||||
t.Errorf("To[int] failed: %v, %v", v, err)
|
t.Errorf("To[int] failed: %v", v)
|
||||||
}
|
}
|
||||||
if v := cast.As[int]("123"); v != 123 {
|
if v := cast.To[int]("abc"); v != 0 {
|
||||||
t.Errorf("As[int] failed: %v", v)
|
t.Errorf("To[int] for invalid input should be 0, got %v", v)
|
||||||
}
|
|
||||||
if v := cast.As[int]("abc"); v != 0 {
|
|
||||||
t.Errorf("As[int] for invalid input should be 0, got %v", v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slice
|
// Slice
|
||||||
s, err := cast.To[[]int]([]string{"1", "2", "3"})
|
s := cast.To[[]int]([]string{"1", "2", "3"})
|
||||||
if err != nil || len(s) != 3 || s[0] != 1 || s[1] != 2 || s[2] != 3 {
|
if len(s) != 3 || s[0] != 1 || s[1] != 2 || s[2] != 3 {
|
||||||
t.Errorf("To[[]int] failed: %v, %v", s, err)
|
t.Errorf("To[[]int] failed: %v", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map
|
// Map
|
||||||
m, err := cast.To[map[string]int](map[string]string{"a": "1", "b": "2"})
|
m := cast.To[map[string]int](map[string]string{"a": "1", "b": "2"})
|
||||||
if err != nil || len(m) != 2 || m["a"] != 1 || m["b"] != 2 {
|
if len(m) != 2 || m["a"] != 1 || m["b"] != 2 {
|
||||||
t.Errorf("To[map[string]int] failed: %v, %v", m, err)
|
t.Errorf("To[map[string]int] failed: %v", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON Auto conversion (Struct to String)
|
// JSON Auto conversion (Struct to String)
|
||||||
@ -34,58 +31,35 @@ func TestToAs(t *testing.T) {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
u := User{Name: "Alice"}
|
u := User{Name: "Alice"}
|
||||||
js, err := cast.To[string](u)
|
js := cast.To[string](u)
|
||||||
if err != nil || js != `{"name":"Alice"}` {
|
if js != `{"name":"Alice"}` {
|
||||||
t.Errorf("To[string] for struct failed: %v, %v", js, err)
|
t.Errorf("To[string] for struct failed: %v", js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON Auto conversion (String to Struct)
|
// JSON Auto conversion (String to Struct)
|
||||||
u2, err := cast.To[User](`{"name":"Bob"}`)
|
u2 := cast.To[User](`{"name":"Bob"}`)
|
||||||
if err != nil || u2.Name != "Bob" {
|
if u2.Name != "Bob" {
|
||||||
t.Errorf("To[User] from string failed: %v, %v", u2, err)
|
t.Errorf("To[User] from string failed: %v", u2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuietMust(t *testing.T) {
|
func TestAsWrapper(t *testing.T) {
|
||||||
// Quiet
|
// As
|
||||||
if v := cast.Quiet(cast.To[int]("abc")); v != 0 {
|
if v := cast.As(123, nil); v != 123 {
|
||||||
t.Errorf("Quiet failed: %v", v)
|
t.Errorf("As failed: %v", v)
|
||||||
}
|
}
|
||||||
if v := cast.Quiet(cast.To[int]("123")); v != 123 {
|
|
||||||
t.Errorf("Quiet failed: %v", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r == nil {
|
|
||||||
t.Errorf("Must should panic on error")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
cast.Must(cast.To[int]("abc"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapSliceAPIs(t *testing.T) {
|
func TestMapSliceAPIs(t *testing.T) {
|
||||||
// ToMap
|
// ToMap
|
||||||
m, err := cast.ToMap[string, int]([]any{"a", "1", "b", 2})
|
m := cast.ToMap[string, int]([]any{"a", "1", "b", 2})
|
||||||
if err != nil || m["a"] != 1 || m["b"] != 2 {
|
if m["a"] != 1 || m["b"] != 2 {
|
||||||
t.Errorf("ToMap failed: %v, %v", m, err)
|
t.Errorf("ToMap failed: %v", m)
|
||||||
}
|
|
||||||
|
|
||||||
// As[map...]
|
|
||||||
m2 := cast.As[map[string]int]([]any{"c", "3"})
|
|
||||||
if m2["c"] != 3 {
|
|
||||||
t.Errorf("As[map] failed: %v", m2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSlice
|
// ToSlice
|
||||||
s, err := cast.ToSlice[int]([]string{"10", "20"})
|
s := cast.ToSlice[int]([]string{"10", "20"})
|
||||||
if err != nil || len(s) != 2 || s[0] != 10 || s[1] != 20 {
|
if len(s) != 2 || s[0] != 10 || s[1] != 20 {
|
||||||
t.Errorf("ToSlice failed: %v, %v", s, err)
|
t.Errorf("ToSlice failed: %v", s)
|
||||||
}
|
|
||||||
|
|
||||||
// As[[]...]
|
|
||||||
s2 := cast.As[[]string]([]int{1, 2})
|
|
||||||
if len(s2) != 2 || s2[0] != "1" || s2[1] != "2" {
|
|
||||||
t.Errorf("As[[]string] failed: %v", s2)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user