diff --git a/AI.md b/AI.md index 1b354bf..07c7141 100644 --- a/AI.md +++ b/AI.md @@ -5,42 +5,39 @@ ## 🤖 AI 行为准则 1. **优先使用 convert.To 进行对象映射**:在需要进行结构体拷贝、Map 转 Struct 或复杂嵌套转换时,直接调用 `convert.To(from, &to)`。 -2. **不再建议手动编写冗余转换代码**:例如不需要手动循环切片来转换元素类型,`convert.To` 会自动完成。 -3. **理解“解包”与“包装”逻辑**:生成代码时可以放心将单值传给切片目标,或将切片传给单值目标。 -4. **利用键名容忍度**:在处理不可控的外部 Map 数据(如 API 响应)时,无需担心键名是 `camelCase` 还是 `snake_case`,`convert.To` 会自动匹配。 +2. **理解“解包”与“包装”逻辑**:生成代码时可以放心将单值传给切片目标,或将切片传给单值目标。 +3. **利用键名容忍度**:在处理不可控的外部数据时,无需担心键名格式(驼峰、蛇形等),`convert.To` 会自动匹配。 +4. **无视指针层级**:在编写调用代码时,无需手动解引用或取地址以匹配类型,`convert` 内部会自动穿透处理。 ## 🛠 关键 API 逻辑约定 | 函数 | 逻辑特征 | | :--- | :--- | -| `To(from, to)` | **主入口**。`to` 必须为指针。支持所有基础类型、容器、函数及嵌套指针。失败会重置 `to` 为零值而不报错。 | -| `normalizeKey` | 内部逻辑:移除所有非字母数字字符并转小写。`My_User-ID` -> `myuserid`。 | -| `Func` 转换 | 动态代理。支持在不同签名的函数间建立桥接,并自动转换输入输出参数。 | -| `FlatStruct` | 工具。返回包含所有平展字段、方法及其 Value 的 `StructInfo`。 | +| `To(from, to)` | **主入口**。要求 `to` 必须为指针。核心逻辑是根据 `to` 的类型强力揉捏 `from`。 | +| `Convert(from, to)` | `To` 的别名。 | +| `FlatStruct(data)` | 获取结构体的扁平化元信息(导出字段/方法)。 | ## 🧩 典型模式 (Best Practices) * **❌ 不推荐 (Standard Go)**: ```go - // 手动循环并转换类型 - dest := make([]int, len(src)) - for i, v := range src { - dest[i] = int(cast.Int(v)) - } - ``` -* **✅ 推荐 (@go/convert)**: - ```go - // 一行搞定,自动处理所有摩擦 - convert.To(src, &dest) - ``` - -* **❌ 不推荐 (Standard Go)**: - ```go - // 手动映射字段 + // 手动映射字段,且对格式敏感 u.UserID = m["user_id"].(int) ``` * **✅ 推荐 (@go/convert)**: ```go - // 自动忽略键名格式差异 + // 自动匹配任何格式的键名 convert.To(m, &u) ``` + +* **❌ 不推荐 (Standard Go)**: + ```go + // 手动处理单值转切片 + var dest []int + dest = append(dest, src) + ``` +* **✅ 推荐 (@go/convert)**: + ```go + // 自动包装 + convert.To(src, &dest) + ``` diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fc4b3bf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog: @go/convert + +## [v1.0.0] - 2026-04-22 + +### Added +- **核心映射引擎**:基于反射的深度对象转换功能,支持 Struct, Map, Slice。 +- **意图优先设计**:支持单值与切片的自动包装/解包转换。 +- **智能字符串转换**:支持将 CSV 格式字符串 (`"a,b,c"`) 自动转为切片。 +- **极致去摩擦匹配**:键名映射时自动忽略大小写及所有非字母数字字符(如 `_`, `-`, `#` 等)。 +- **函数桥接器**:支持将一个函数转换为另一个签名的函数,并自动处理参数转换。 +- **Parse 钩子**:支持通过 `ParseXxx` 方法定制特定字段的转换逻辑。 +- **平展工具**:提供 `FlatStruct` 辅助函数用于获取结构体的扁平化字段信息。 +- **接口兼容**:原生支持 `json.Unmarshaler` 和 `yaml.Unmarshaler`。 diff --git a/README.md b/README.md index 87674a7..488c35b 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,34 @@ ## 🎯 设计哲学 -* **消除类型摩擦**:在业务代码中,我们经常遇到单值与切片、字符串与结构体之间的转换。`convert` 能够自动处理这些“形状”差异(如将单值包装成切片,或将切片解包为单值)。 -* **极致容忍 Key 名**:Map 键名可能是 `user_id`,也可能是 `UserId`。`convert` 在映射时会忽略所有非字母数字字符及大小写,确保映射成功。 -* **指针透明化**:无论是 `int` 映射到 `*string`,还是 `***int` 映射到 `int`,`convert` 都会自动处理深层指针的穿透与分配。 -* **可定制转换**:支持 `ParseXxx` 方法作为转换钩子,允许对象在被映射时执行特定的解析逻辑。 +* **消除类型摩擦**:在业务代码中,我们经常遇到单值与切片、字符串与结构体之间的转换。`convert` 能够自动处理这些“形状”差异。 +* **极致容忍 Key 名**:忽略所有非字母数字字符及大小写,确保不同来源的数据都能精准映射。 +* **指针透明化**:自动处理深层指针的穿透与分配。 +* **可定制转换**:通过方法钩子实现特定的解析逻辑。 -## 🚀 核心特性 +## 🛠 API Reference -* **深度映射**:支持 Struct、Map、Slice 之间的无限层级嵌套转换。 -* **智能切片转换**: - * 单值 -> 切片:自动包装为 `[]T{val}`。 - * 切片 -> 单值:自动取首个元素。 - * CSV 字符串 -> 切片:支持 `"1,2,3"` 风格的自动拆分。 -* **函数转换**:支持将一个函数转换为另一个签名的函数,并在调用时自动转换参数和返回值。 -* **接口支持**:兼容 `json.Unmarshaler` 和 `yaml.Unmarshaler`。 -* **平展工具**:提供 `FlatStruct` 工具,可将复杂的嵌套结构体平展为扁平的字段列表。 +### 核心函数 + +#### `func To(from, to any)` +将 `from` 中的数据深度映射到 `to` 中。`to` 必须是一个**指针**类型。 +- **支持类型**:基础类型互转、Struct 互转、Map 转 Struct、Struct 转 Map、Slice 互转等。 +- **去摩擦特性**:支持单值与切片的互转(包装/解包)、CSV 字符串转切片。 + +#### `func Convert(from, to any)` +`To` 的别名,用于保持向前兼容。 + +### 结构体分析 + +#### `func FlatStruct(data any) *StructInfo` +平展结构体。返回导出字段、导出方法及其对应的 `reflect.Value` 映射。 + +#### `func FlatStructWithUnexported(data any) *StructInfo` +平展结构体,包含未导出的字段和方法。 + +### 定制转换钩子 + +如果目标结构体定义了 `func (p *T) ParseXxx(v any) FieldType` 方法(其中 `Xxx` 为字段名),`convert` 将优先调用该方法来决定字段的值。 ## 📦 安装 @@ -31,25 +44,13 @@ go get apigo.cc/go/convert ```go import "apigo.cc/go/convert" -// 1. 极致去摩擦的键名匹配 -from := map[string]any{"user_id": 1001, "USER-NAME": "Andy"} -type User struct { UserID int; UserName string } -var u User -convert.To(from, &u) // u.UserID = 1001, u.UserName = "Andy" +// 1. 模糊键名匹配 +from := map[string]any{"user-id": 1001} +var u struct { UserID int } +convert.To(from, &u) // u.UserID = 1001 // 2. 切片自动解包 nums := []int{100, 200} var n int convert.To(nums, &n) // n = 100 - -// 3. 智能 CSV 解析 -tags := "tag1, tag2, tag3" -var tagList []string -convert.To(tags, &tagList) // ["tag1", "tag2", "tag3"] - -// 4. 函数动态映射 -f1 := func(a int) int { return a + 1 } -var f2 func(string) string -convert.To(f1, &f2) -fmt.Println(f2("10")) // "11" ``` diff --git a/TEST.md b/TEST.md index abfee02..9d1b966 100644 --- a/TEST.md +++ b/TEST.md @@ -22,8 +22,3 @@ | 函数 | 平均耗时 | 性能分析 | | :--- | :--- | :--- | | `MatrixConvert` | **1226 ns/op** | 包含反射解析、Map 键名归一化及嵌套映射,性能处于工业级水准。 | - -## 🔍 Self-Review 修正记录 -1. **代码纠错**:移除了未使用变量 `fromArg`。 -2. **逻辑补强**:在基础类型转换分支中引入了 `effectiveFrom` 解包逻辑,支持切片到单值的自动转换。 -3. **命名一致性**:统一使用 `To` 作为主入口,`Convert` 作为别名。 diff --git a/go.mod b/go.mod index 76cfc87..ba3005f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module apigo.cc/go/convert go 1.25.0 require ( - apigo.cc/go/cast v1.0.0 + apigo.cc/go/cast v1.0.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b1d8d9b..1e0bebf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -apigo.cc/go/cast v1.0.0 h1:MhkWBDMq8ewAxn5PYHUlIuwpfsW5bQS6ueptBkim5hc= -apigo.cc/go/cast v1.0.0/go.mod h1:vh9ZqISCmTUiyinkNMI/s4f045fRlDK3xC+nPWQYBzI= +apigo.cc/go/cast v1.0.1 h1:OCQepSPf+wQBawUc4LB0hv4WegWyyz66qELsKuzzl6I= +apigo.cc/go/cast v1.0.1/go.mod h1:vh9ZqISCmTUiyinkNMI/s4f045fRlDK3xC+nPWQYBzI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=