refactor: optimize zero-friction API (v1.2.0) - frictionless To[T], error-returning specialized APIs, detailed README (by AI)

This commit is contained in:
AI Engineer 2026-05-04 11:24:07 +08:00
parent 684a33e0e5
commit 9bf5445d1d
6 changed files with 48 additions and 41 deletions

View File

@ -7,10 +7,10 @@
`@go/cast` 是一个为“极致敏捷”设计的 Go 基础工具库。其核心目标是**彻底消除摩擦** `@go/cast` 是一个为“极致敏捷”设计的 Go 基础工具库。其核心目标是**彻底消除摩擦**
* **零错误返回**:核心 API 不返回 `error`在失败或非法转换时静默返回类型零值。 * **万能零摩擦入口**`To[T]` 作为核心 API永不返回 `error`在失败或非法转换时静默返回类型零值。
* **语义化 As 包装**:提供 `As` 函数用于将传统“值+错误”双返回结果一键转化为单值。 * **语义化 As 包装**:提供 `As` 函数用于将传统“值+错误”双返回结果一键转化为单值,消除外部库带来的摩擦
* **智能自动穿透**:万能 `To[T]` 自动识别 JSON 文本、复杂容器映射、指针穿透等场景 * **显式错误支持**:底层 API`FromJSON`, `ToMap` 等)保留 `error` 返回,供需要严谨校验的场景使用
* **极致性能**:内置 FastEncoder/FastDecoder单路径处理最小化内存分配 * **智能自动穿透**`To[T]` 自动识别 JSON 文本、复杂容器映射、指针穿透等场景
## 📦 安装 ## 📦 安装
@ -38,35 +38,42 @@ js := cast.To[string](map[string]int{"age": 18}) // `{"age":18}`
user := cast.To[User](`{"name":"Tom"}`) user := cast.To[User](`{"name":"Tom"}`)
// 4. 复杂容器转换 (Map/Slice) // 4. 复杂容器转换 (Map/Slice)
m := cast.ToMap[string, int]([]any{"id", "1", "score", 100}) m, _ := cast.ToMap[string, int]([]any{"id", "1"})
list := cast.To[[]int]([]string{"1", "2", "3"}) list, _ := cast.ToSlice[int]([]string{"1", "2", "3"})
``` ```
## 🛠 API 指南 ## 🛠 API 指南
### 核心 API ### 核心 API
1. **通用转换** 1. **通用转换 (Frictionless)**
* `To[T any](any) T` —— 万能转换入口。支持基础类型、Slice、Map 以及 JSON 的双向自动转换。失败时返回类型零值。 * `To[T any](any) T` —— 万能转换入口。支持基础类型、Slice、Map 以及 JSON 的双向自动转换。失败时返回类型零值。
* 支持矩阵:
* `string/[]byte` <-> `struct/map/slice` (自动 JSON 编解码)
* `map` <-> `struct` (字段名智能匹配)
* `[]any` <-> `map` (KV 序列展开/折叠)
* 所有基础类型 (`int`, `string`, `bool`, `float`, `duration`) 互相转换。
2. **错误处理工具**
* `As[T any](v T, err error) T` —— 错误消除工具。将传统的 `(value, error)` 返回值包装为单值。 * `As[T any](v T, err error) T` —— 错误消除工具。将传统的 `(value, error)` 返回值包装为单值。
2. **容器转换** 3. **容器转换 (Strict)**
* `ToMap[K, V](any) map[K]V` —— 构建新 Map。 * `ToMap[K, V](any) (map[K]V, error)` —— 构建新 Map。
* `ToSlice[T](any) []T` —— 构建新 Slice。 * `ToSlice[T](any) ([]T, error)` —— 构建新 Slice。
* `FillMap(target, source)` | `FillSlice(target, source)` —— 填充现有容器。 * `FillMap(target, source)` | `FillSlice(target, source)` —— 填充现有容器。
3. **JSON 序列化与构建** 4. **JSON 序列化与构建 (Strict)**
* **编码**: `ToJSON(any) string` | `ToJSONBytes(any) []byte` * **编码**: `ToJSON(any) (string, error)` | `ToJSONBytes(any) ([]byte, error)`
* **解码**: `UnmarshalJSON(data, target) error` (底层 API保留 error 用于调试) | `FromJSON[T](any) T` * **解码**: `UnmarshalJSON(data, target) error` | `FromJSON[T](any) (T, error)`
* **进阶**: `ToJSONDesensitizeBytes(any, []string) []byte` (支持字段脱敏) | `PrettyToJSON(any) string` * **其他**: `ToJSONDesensitizeBytes(any, []string) ([]byte, error)` | `PrettyToJSON(any) string`
4. **泛型工具** 5. **泛型工具**
* `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. **基础转换 (直接调用,极致性能)** 6. **基础转换 (直接调用,极致性能)**
* `Int`, `Int64`, `Uint`, `Uint64`, `Float`, `Float64`, `String`, `Bool`, `Duration` * `Int`, `Int64`, `Uint`, `Uint64`, `Float`, `Float64`, `String`, `Bool`, `Duration`
## 🧪 验证状态 ## 🧪 验证状态

View File

@ -146,7 +146,6 @@ func BenchmarkToJSON_SimpleStruct(b *testing.B) {
u := User{ID: 1, Name: "Benchmark User"} u := User{ID: 1, Name: "Benchmark User"}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
// 这里包含了你特有的 makeJSONType 清洗逻辑和 FixUpperCase 逻辑
_ = cast.To[string](u) _ = cast.To[string](u)
} }
} }

39
cast.go
View File

@ -47,17 +47,16 @@ func RealValue(v reflect.Value) reflect.Value {
// To 泛型转换 (支持基础类型、Slice、Map 及 JSON 自动转换,零摩擦模式) // To 泛型转换 (支持基础类型、Slice、Map 及 JSON 自动转换,零摩擦模式)
func To[T any](v any) T { func To[T any](v any) T {
var zero T
targetType := reflect.TypeOf((*T)(nil)).Elem() targetType := reflect.TypeOf((*T)(nil)).Elem()
// 1. 处理 JSON 自动转换 (Input: string/[]byte, Target: struct/map/slice) // 1. 处理 JSON 自动转换 (Input: string/[]byte, Target: struct/map/slice)
if isJSONText(v) && isComplexType(targetType) { if isJSONText(v) && isComplexType(targetType) {
return FromJSON[T](v) return As(FromJSON[T](v))
} }
// 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 := ToJSON(v) s := As(ToJSON(v))
return any(reflectCast(s, targetType).Interface()).(T) return any(reflectCast(s, targetType).Interface()).(T)
} }
@ -76,8 +75,9 @@ func To[T any](v any) T {
} }
// 4. 处理基础类型 // 4. 处理基础类型
res := reflectCast(v, targetType) res, err := reflectCastE(v, targetType)
if !res.IsValid() { if err != nil {
var zero T
return zero return zero
} }
return res.Interface().(T) return res.Interface().(T)
@ -432,20 +432,21 @@ func Duration(value any) time.Duration {
return 0 return 0
} }
func ToJSONBytes(value any) []byte { func ToJSONBytes(value any) ([]byte, error) {
return As(fastToJSONBytes(value)) return fastToJSONBytes(value)
} }
func ToJSONDesensitizeBytes(value any, keys []string) []byte { func ToJSONDesensitizeBytes(value any, keys []string) ([]byte, error) {
return As(fastToJSONBytes(value, keys...)) return fastToJSONBytes(value, keys...)
} }
func ToJSON(value any) string { func ToJSON(value any) (string, error) {
return string(ToJSONBytes(value)) b, err := ToJSONBytes(value)
return string(b), err
} }
func PrettyToJSON(value any) string { func PrettyToJSON(value any) string {
j := ToJSONBytes(value) j := As(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.String() return r.String()
@ -474,10 +475,10 @@ func UnmarshalJSON(data any, value any) error {
return fastUnmarshalJSONBytes(b, value) return fastUnmarshalJSONBytes(b, value)
} }
func FromJSON[T any](data any) T { func FromJSON[T any](data any) (T, error) {
var v T var v T
_ = UnmarshalJSON(data, &v) err := UnmarshalJSON(data, &v)
return v return v, err
} }
// --- Others (Keep logic but clean style) --- // --- Others (Keep logic but clean style) ---
@ -578,17 +579,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 { func ToMap[K comparable, V any](source any) (map[K]V, error) {
m := make(map[K]V) m := make(map[K]V)
fillToMap(reflect.ValueOf(m), source) fillToMap(reflect.ValueOf(m), source)
return m return m, nil
} }
// ToSlice 泛型构建新 Slice // ToSlice 泛型构建新 Slice
func ToSlice[T any](source any) []T { func ToSlice[T any](source any) ([]T, error) {
var s []T var s []T
fillToSlice(reflect.ValueOf(&s).Elem(), source) fillToSlice(reflect.ValueOf(&s).Elem(), source)
return s return s, nil
} }
// FillMap 将 source 填充到目标 map 中 (兼容旧 API 逻辑) // FillMap 将 source 填充到目标 map 中 (兼容旧 API 逻辑)

View File

@ -229,7 +229,7 @@ func TestToJSONDesensitizeBytes(t *testing.T) {
u := User{Name: "Tom", Password: "secret123", Age: 18} u := User{Name: "Tom", Password: "secret123", Age: 18}
// 测试脱敏功能 // 测试脱敏功能
b := cast.ToJSONDesensitizeBytes(u, []string{"password"}) b := cast.As(cast.ToJSONDesensitizeBytes(u, []string{"password"}))
js := string(b) js := string(b)
if !strings.Contains(js, `"password":"***"`) { if !strings.Contains(js, `"password":"***"`) {

View File

@ -23,7 +23,7 @@ func TestToMap(t *testing.T) {
} }
// Test slice-to-map (KV) // Test slice-to-map (KV)
m2 := cast.ToMap[int, string]([]any{"1", "a", 2, 200}) m2 := cast.As(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,7 +58,7 @@ func TestJSON(t *testing.T) {
} }
// Test FromJSON // Test FromJSON
c2 := cast.FromJSON[Config]([]byte(data)) c2 := cast.As(cast.FromJSON[Config]([]byte(data)))
if c2.Port != 8080 { if c2.Port != 8080 {
t.Errorf("FromJSON failed: %v", c2) t.Errorf("FromJSON failed: %v", c2)
} }

View File

@ -52,13 +52,13 @@ func TestAsWrapper(t *testing.T) {
func TestMapSliceAPIs(t *testing.T) { func TestMapSliceAPIs(t *testing.T) {
// ToMap // ToMap
m := cast.ToMap[string, int]([]any{"a", "1", "b", 2}) m := cast.As(cast.ToMap[string, int]([]any{"a", "1", "b", 2}))
if m["a"] != 1 || m["b"] != 2 { if m["a"] != 1 || m["b"] != 2 {
t.Errorf("ToMap failed: %v", m) t.Errorf("ToMap failed: %v", m)
} }
// ToSlice // ToSlice
s := cast.ToSlice[int]([]string{"10", "20"}) s := cast.As(cast.ToSlice[int]([]string{"10", "20"}))
if len(s) != 2 || s[0] != 10 || s[1] != 20 { if len(s) != 2 || s[0] != 10 || s[1] != 20 {
t.Errorf("ToSlice failed: %v", s) t.Errorf("ToSlice failed: %v", s)
} }