feat: 增强 nil 值处理能力

主要改进:
- 简化 setValue 方法中的 nil 值处理逻辑,统一设置为目标类型的零值
- 修复 setFieldValue 方法中 map 类型设置 nil 值时的问题
- 新增完整的 nil 值设置测试,覆盖所有 Go 基础类型和复合类型

测试覆盖:
- 指针、接口、切片、map、channel、func 等可空类型设置为 nil
- int/uint 系列、float 系列、bool、string 等基础类型设置为零值
- 嵌套结构体和 map 中的 nil 值设置
This commit is contained in:
2025-12-09 15:11:18 +08:00
parent 6e3593f188
commit fbba6f9a30
2 changed files with 415 additions and 90 deletions

178
rfx.go
View File

@@ -209,6 +209,13 @@ func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool {
if target.IsNil() {
target.Set(reflect.MakeMap(target.Type()))
}
// 处理 nil 值的情况
if v == nil {
target.SetMapIndex(reflect.ValueOf(key), reflect.Zero(target.Type().Elem()))
return true
}
val := reflect.ValueOf(v)
if !val.Type().AssignableTo(target.Type().Elem()) {
// 尝试转换
@@ -275,106 +282,97 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
targetType := field.Type()
if !val.IsValid() {
// 如果值无效(通常是 nil 的情况)
// 对于可以为 nil 的类型,直接设置零值
switch targetType.Kind() {
case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
field.Set(reflect.Zero(targetType))
return true
default:
// 对于基础类型,继续使用后面的 cast 转换逻辑
// 不提前返回 false
}
} else {
// 如果 val 有效,进行正常的类型处理
// 统一解开最外层的 interface 包装,便于后续根据底层实际类型做处理
for val.Kind() == reflect.Interface && !val.IsNil() {
val = val.Elem()
}
val = reflect.Zero(targetType)
}
// 尝试直接赋值(类型完全匹配)
// 如果 val 有效,进行正常的类型处理
// 统一解开最外层的 interface 包装,便于后续根据底层实际类型做处理
for val.Kind() == reflect.Interface && !val.IsNil() {
val = val.Elem()
}
// 尝试直接赋值(类型完全匹配)
if val.Type().AssignableTo(targetType) {
field.Set(val)
return true
}
// 如果源值是指针但目标不是指针,尝试解引用后再赋值
if val.Kind() == reflect.Ptr && !val.IsNil() && targetType.Kind() != reflect.Ptr {
derefVal := val.Elem()
if derefVal.Type().AssignableTo(targetType) {
field.Set(derefVal)
return true
}
// 解引用后继续使用下面的逻辑处理
val = derefVal
}
switch targetType.Kind() {
case reflect.Ptr:
// 处理指针类型
// 如果传入的值已经是指针类型,尝试直接赋值
if val.Type().AssignableTo(targetType) {
field.Set(val)
return true
}
// 如果源值是指针但目标不是指针,尝试解引用后再赋
if val.Kind() == reflect.Ptr && !val.IsNil() && targetType.Kind() != reflect.Ptr {
derefVal := val.Elem()
if derefVal.Type().AssignableTo(targetType) {
field.Set(derefVal)
return true
}
// 解引用后继续使用下面的逻辑处理
val = derefVal
// 如果传入的值不是指针,创建新指针并设置
elemType := targetType.Elem()
newPtr := reflect.New(elemType)
// 递归设置指针指向的值
if !r.setValue(newPtr.Elem(), v) {
return false
}
switch targetType.Kind() {
case reflect.Ptr:
// 处理指针类型
// 如果传入的值已经是指针类型,尝试直接赋值
if val.Type().AssignableTo(targetType) {
field.Set(val)
return true
}
// 如果传入的值不是指针,创建新指针并设置值
elemType := targetType.Elem()
newPtr := reflect.New(elemType)
// 递归设置指针指向的值
if !r.setValue(newPtr.Elem(), v) {
return false
}
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
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 进行智能类型转换