feat: 优化 setValue 方法支持 nil 值处理
改进 setValue 方法在处理 nil 值时的行为: - 对于可空类型(指针、接口、切片、map 等)设置为零值而非失败 - 对于基础类型(int、string、bool 等)继续执行 cast 转换逻辑 - 修复 val 无效时调用 val.Type() 的潜在空指针问题
This commit is contained in:
parent
2b7ac003a8
commit
6e3593f188
156
rfx.go
156
rfx.go
@ -272,99 +272,109 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
|
||||
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(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)
|
||||
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 = derefVal
|
||||
}
|
||||
|
||||
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
|
||||
// 如果源值是指针但目标不是指针,尝试解引用后再赋值
|
||||
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
|
||||
}
|
||||
|
||||
field.Set(newPtr)
|
||||
return true
|
||||
switch targetType.Kind() {
|
||||
case reflect.Ptr:
|
||||
// 处理指针类型
|
||||
// 如果传入的值已经是指针类型,尝试直接赋值
|
||||
if val.Type().AssignableTo(targetType) {
|
||||
field.Set(val)
|
||||
return true
|
||||
}
|
||||
|
||||
case reflect.Slice: // 处理切片类型,支持从通用切片(如 []any)转换
|
||||
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
|
||||
return false
|
||||
}
|
||||
// 如果传入的值不是指针,创建新指针并设置值
|
||||
elemType := targetType.Elem()
|
||||
newPtr := reflect.New(elemType)
|
||||
|
||||
newSlice := reflect.MakeSlice(targetType, val.Len(), val.Len())
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if !r.setValue(newSlice.Index(i), val.Index(i)) {
|
||||
// 递归设置指针指向的值
|
||||
if !r.setValue(newPtr.Elem(), v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
field.Set(newSlice)
|
||||
field.Set(newPtr)
|
||||
return true
|
||||
|
||||
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) {
|
||||
case reflect.Slice: // 处理切片类型,支持从通用切片(如 []any)转换
|
||||
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
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 进行智能类型转换
|
||||
@ -406,7 +416,7 @@ func (r *rfx) setValue(field reflect.Value, v any) bool {
|
||||
|
||||
if err != nil {
|
||||
// 如果 cast 失败,尝试标准的反射类型转换作为后备
|
||||
if val.Type().ConvertibleTo(field.Type()) {
|
||||
if val.IsValid() && val.Type().ConvertibleTo(field.Type()) {
|
||||
field.Set(val.Convert(field.Type()))
|
||||
return true
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user