feat: 增强类型转换能力和代码重构

核心改进:
1. 新增 normalizeInputValue 函数
   - 统一处理输入值的规范化,支持 R 接口、[]R 切片、reflect.Value
   - 避免重复封装,提升性能和类型安全性

2. 重构 getValueByPath 为独立函数
   - 从 rfx 方法提取为独立函数,提高代码复用性
   - 更好的职责分离,便于维护和测试

3. 显著增强 setValue 方法的类型转换能力
   - 支持切片类型转换:[]any -> []T,自动转换每个元素
   - 支持结构体类型转换:map -> struct 或 struct -> struct,按字段名匹配
   - 保持指针切片的引用语义,避免不必要的对象复制

4. 新增 tryMapField 函数
   - 支持 Map 键名的大小写不敏感访问
   - 首字母大写的键会自动尝试小写版本(如 "Host" -> "host")

5. 新增 lowercaseFirst 辅助函数
   - 用于首字母小写转换,配合 Map 键名查找

6. 更新测试用例
   - 新增指针切片的使用示例
   - 展示 []any 包含指针元素的场景

7. 文档全面更新
   - 新增"高级类型转换"章节,详细说明切片、结构体、指针切片等转换
   - 更新特性列表,突出增强的类型转换能力和 R 接口集成
   - 补充 Map 大小写不敏感访问的说明

影响范围:
- reflux.go: 使用 normalizeInputValue 统一输入处理
- rfx.go: 重构并增强 setValue、getValueByPath 等核心方法
- util.go: 新增多个辅助函数,代码行数增加 160 行
- rfx_example_test.go: 新增指针切片测试用例

向后兼容:完全兼容现有 API,仅增强内部实现和类型转换能力
This commit is contained in:
2025-12-05 11:12:11 +08:00
parent 243d1ffac8
commit 9e1bc55a8c
5 changed files with 415 additions and 77 deletions

140
rfx.go
View File

@@ -20,12 +20,12 @@ type rfx struct {
// Get 通过路径获取嵌套字段的值
func (r *rfx) Get(p ...string) R {
return &rfx{value: r.getValueByPath(p...)}
return &rfx{value: getValueByPath(r.value, p...)}
}
// Scope 创建一个指定路径的作用域视图(深度克隆)
func (r *rfx) Scope(p ...string) R {
v := r.getValueByPath(p...)
v := getValueByPath(r.value, p...)
if !v.IsValid() {
return &rfx{value: reflect.Value{}}
}
@@ -35,56 +35,6 @@ func (r *rfx) Scope(p ...string) R {
return &rfx{value: cloned}
}
// getValueByPath 通过路径获取值的辅助方法
// 支持两种路径格式:
// 1. 多个参数: Get("Address", "City")
// 2. 点号分割: Get("Address.City") 或混合使用 Get("Address.City", "ZipCode")
func (r *rfx) getValueByPath(p ...string) reflect.Value {
v := r.value
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
// 展开所有路径片段,支持点号分割
keys := expandPath(p...)
for _, key := range keys {
if !v.IsValid() {
return reflect.Value{}
}
switch v.Kind() {
case reflect.Struct:
v = tryStructField(v, key)
case reflect.Map:
v = v.MapIndex(reflect.ValueOf(key))
case reflect.Slice, reflect.Array:
// 尝试将 key 转换为索引
idx, err := strconv.Atoi(key)
if err == nil && idx >= 0 && idx < v.Len() {
v = v.Index(idx)
} else {
return reflect.Value{}
}
default:
return reflect.Value{}
}
// 解引用指针和接口
for v.IsValid() && (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
}
return v
}
// Set 设置指定路径的值,支持链式调用
// 如果路径不存在或设置失败,会 panic
func (r *rfx) Set(key string, v any) R {
@@ -235,7 +185,7 @@ func (r *rfx) getParentValue(p ...string) reflect.Value {
}
return v
}
return r.getValueByPath(p...)
return getValueByPath(r.value, p...)
}
// setFieldValue 设置字段值的辅助方法
@@ -315,24 +265,40 @@ func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool {
// setValue 设置值的辅助方法
// 使用 cast 库进行智能类型转换,支持更多的转换场景
func (r *rfx) setValue(field reflect.Value, v any) bool {
val := reflect.ValueOf(v)
val, _, err := normalizeInputValue(v)
if err != nil {
return false
}
if !val.IsValid() {
return false
}
// 统一解开最外层的 interface 包装,便于后续根据底层实际类型做处理
for val.Kind() == reflect.Interface && !val.IsNil() {
val = val.Elem()
}
targetType := field.Type()
// 尝试直接赋值(类型完全匹配)
if val.Type().AssignableTo(field.Type()) {
if val.Type().AssignableTo(targetType) {
field.Set(val)
return true
}
// 处理指针类型
if field.Type().Kind() == reflect.Ptr {
switch targetType.Kind() {
case reflect.Ptr:
// 处理指针类型
// 如果传入的值已经是指针类型,尝试直接赋值
if val.Type().AssignableTo(field.Type()) {
if val.Type().AssignableTo(targetType) {
field.Set(val)
return true
}
// 如果传入的值不是指针,创建新指针并设置值
elemType := field.Type().Elem()
elemType := targetType.Elem()
newPtr := reflect.New(elemType)
// 递归设置指针指向的值
@@ -342,13 +308,56 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
field.Set(newPtr)
return true
case reflect.Slice: // 处理切片类型,支持从通用切片(如 []any)转换
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
return false
}
newSlice := reflect.MakeSlice(targetType, val.Len(), val.Len())
for i := 0; i < val.Len(); i++ {
if !r.setValue(newSlice.Index(i), val.Index(i)) {
return false
}
}
field.Set(newSlice)
return true
case reflect.Struct:
// 遍历目标结构体的字段,从源值(结构体或 map)中按字段名取值并设置
// 仅支持从 struct 或 map 填充
if val.Kind() != reflect.Struct && val.Kind() != reflect.Map {
return false
}
fieldType := field.Type()
for i := 0; i < fieldType.NumField(); i++ {
dstField := field.Field(i)
if !dstField.CanSet() {
continue
}
valField := getValueByPath(val, fieldType.Field(i).Name)
if !valField.IsValid() {
continue
}
// 使用 setValue 复用现有的类型转换逻辑,忽略单个字段失败
if !r.setValue(dstField, valField) {
return false
}
}
return true
}
// 优先使用 cast 进行智能类型转换
// 这样可以处理 string <-> number, number <-> bool 等常见转换
targetType := field.Type()
var converted any
var err error
switch targetType.Kind() {
case reflect.Bool:
@@ -380,11 +389,6 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
case reflect.String:
converted, err = cast.ToStringE(v)
default:
// 对于 cast 不支持的类型,尝试标准的反射类型转换
if val.Type().ConvertibleTo(field.Type()) {
field.Set(val.Convert(field.Type()))
return true
}
return false
}
@@ -398,6 +402,7 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
}
field.Set(reflect.ValueOf(converted))
return true
}
@@ -453,8 +458,7 @@ func (r *rfx) Delete(p ...string) R {
// Exists 检查指定路径的值是否存在
func (r *rfx) Exists(p ...string) bool {
v := r.getValueByPath(p...)
return v.IsValid()
return getValueByPath(r.value, p...).IsValid()
}
// Array 将当前值转换为 R 切片