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 基础工具库。其核心目标是**彻底消除摩擦**
* **零错误返回**:核心 API 不返回 `error`在失败或非法转换时静默返回类型零值。
* **语义化 As 包装**:提供 `As` 函数用于将传统“值+错误”双返回结果一键转化为单值。
* **智能自动穿透**:万能 `To[T]` 自动识别 JSON 文本、复杂容器映射、指针穿透等场景
* **极致性能**:内置 FastEncoder/FastDecoder单路径处理最小化内存分配
* **万能零摩擦入口**`To[T]` 作为核心 API永不返回 `error`在失败或非法转换时静默返回类型零值。
* **语义化 As 包装**:提供 `As` 函数用于将传统“值+错误”双返回结果一键转化为单值,消除外部库带来的摩擦
* **显式错误支持**:底层 API`FromJSON`, `ToMap` 等)保留 `error` 返回,供需要严谨校验的场景使用
* **智能自动穿透**`To[T]` 自动识别 JSON 文本、复杂容器映射、指针穿透等场景
## 📦 安装
@ -38,35 +38,42 @@ js := cast.To[string](map[string]int{"age": 18}) // `{"age":18}`
user := cast.To[User](`{"name":"Tom"}`)
// 4. 复杂容器转换 (Map/Slice)
m := cast.ToMap[string, int]([]any{"id", "1", "score", 100})
list := cast.To[[]int]([]string{"1", "2", "3"})
m, _ := cast.ToMap[string, int]([]any{"id", "1"})
list, _ := cast.ToSlice[int]([]string{"1", "2", "3"})
```
## 🛠 API 指南
### 核心 API
1. **通用转换**
1. **通用转换 (Frictionless)**
* `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)` 返回值包装为单值。
2. **容器转换**
* `ToMap[K, V](any) map[K]V` —— 构建新 Map。
* `ToSlice[T](any) []T` —— 构建新 Slice。
3. **容器转换 (Strict)**
* `ToMap[K, V](any) (map[K]V, error)` —— 构建新 Map。
* `ToSlice[T](any) ([]T, error)` —— 构建新 Slice。
* `FillMap(target, source)` | `FillSlice(target, source)` —— 填充现有容器。
3. **JSON 序列化与构建**
* **编码**: `ToJSON(any) string` | `ToJSONBytes(any) []byte`
* **解码**: `UnmarshalJSON(data, target) error` (底层 API保留 error 用于调试) | `FromJSON[T](any) T`
* **进阶**: `ToJSONDesensitizeBytes(any, []string) []byte` (支持字段脱敏) | `PrettyToJSON(any) string`
4. **JSON 序列化与构建 (Strict)**
* **编码**: `ToJSON(any) (string, error)` | `ToJSONBytes(any) ([]byte, error)`
* **解码**: `UnmarshalJSON(data, target) error` | `FromJSON[T](any) (T, error)`
* **其他**: `ToJSONDesensitizeBytes(any, []string) ([]byte, error)` | `PrettyToJSON(any) string`
4. **泛型工具**
5. **泛型工具**
* `If[T any](bool, T, T) T` —— 三元逻辑。
* `In[T comparable]([]T, T) bool` —— 包含判断。
* `Ptr[T any](T) *T` —— 快速取指针。
* `ArrayToBoolMap[T comparable]([]T) map[T]bool` —— 快速构建索引 Map。
5. **基础转换 (直接调用,极致性能)**
6. **基础转换 (直接调用,极致性能)**
* `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"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 这里包含了你特有的 makeJSONType 清洗逻辑和 FixUpperCase 逻辑
_ = cast.To[string](u)
}
}

39
cast.go
View File

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

View File

@ -229,7 +229,7 @@ func TestToJSONDesensitizeBytes(t *testing.T) {
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)
if !strings.Contains(js, `"password":"***"`) {

View File

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

View File

@ -52,13 +52,13 @@ func TestAsWrapper(t *testing.T) {
func TestMapSliceAPIs(t *testing.T) {
// 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 {
t.Errorf("ToMap failed: %v", m)
}
// 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 {
t.Errorf("ToSlice failed: %v", s)
}