diff --git a/rfx.go b/rfx.go index dccd0ec..c39503c 100644 --- a/rfx.go +++ b/rfx.go @@ -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 进行智能类型转换 diff --git a/rfx_nil_test.go b/rfx_nil_test.go new file mode 100644 index 0000000..449c0ee --- /dev/null +++ b/rfx_nil_test.go @@ -0,0 +1,327 @@ +package reflux + +import ( + "testing" +) + +// TestSetValueWithNil 测试 setValue 方法处理 nil 值的各种情况 +func TestSetValueWithNil(t *testing.T) { + t.Run("Set nil to pointer field", func(t *testing.T) { + type Data struct { + Ptr *string + } + data := Data{Ptr: stringPtr("initial")} + rfx := New(&data) + + rfx.Set("Ptr", nil) + if data.Ptr != nil { + t.Errorf("Expected Ptr to be nil, got %v", data.Ptr) + } + }) + + t.Run("Set nil to interface field", func(t *testing.T) { + type Data struct { + Iface interface{} + } + data := Data{Iface: "initial"} + rfx := New(&data) + + rfx.Set("Iface", nil) + if data.Iface != nil { + t.Errorf("Expected Iface to be nil, got %v", data.Iface) + } + }) + + t.Run("Set nil to slice field", func(t *testing.T) { + type Data struct { + Slice []string + } + data := Data{Slice: []string{"a", "b"}} + rfx := New(&data) + + rfx.Set("Slice", nil) + if data.Slice != nil { + t.Errorf("Expected Slice to be nil, got %v", data.Slice) + } + }) + + t.Run("Set nil to map field", func(t *testing.T) { + type Data struct { + Map map[string]string + } + data := Data{Map: map[string]string{"key": "value"}} + rfx := New(&data) + + rfx.Set("Map", nil) + if data.Map != nil { + t.Errorf("Expected Map to be nil, got %v", data.Map) + } + }) + + t.Run("Set nil to int field - should set to zero value", func(t *testing.T) { + type Data struct { + Value int + } + data := Data{Value: 42} + rfx := New(&data) + + rfx.Set("Value", nil) + if data.Value != 0 { + t.Errorf("Expected Value to be 0, got %d", data.Value) + } + }) + + t.Run("Set nil to string field - should set to zero value", func(t *testing.T) { + type Data struct { + Value string + } + data := Data{Value: "hello"} + rfx := New(&data) + + rfx.Set("Value", nil) + if data.Value != "" { + t.Errorf("Expected Value to be empty string, got %s", data.Value) + } + }) + + t.Run("Set nil to bool field - should set to zero value", func(t *testing.T) { + type Data struct { + Value bool + } + data := Data{Value: true} + rfx := New(&data) + + rfx.Set("Value", nil) + if data.Value != false { + t.Errorf("Expected Value to be false, got %v", data.Value) + } + }) + + t.Run("Set nil to float64 field - should set to zero value", func(t *testing.T) { + type Data struct { + Value float64 + } + data := Data{Value: 3.14} + rfx := New(&data) + + rfx.Set("Value", nil) + if data.Value != 0.0 { + t.Errorf("Expected Value to be 0.0, got %f", data.Value) + } + }) + + t.Run("Set nil to uint field - should set to zero value", func(t *testing.T) { + type Data struct { + Value uint + } + data := Data{Value: 100} + rfx := New(&data) + + rfx.Set("Value", nil) + if data.Value != 0 { + t.Errorf("Expected Value to be 0, got %d", data.Value) + } + }) + + t.Run("Set nil to nested pointer field", func(t *testing.T) { + type Inner struct { + Value string + } + type Outer struct { + Inner *Inner + } + data := Outer{Inner: &Inner{Value: "test"}} + rfx := New(&data) + + rfx.Set("Inner", nil) + if data.Inner != nil { + t.Errorf("Expected Inner to be nil, got %v", data.Inner) + } + }) + + t.Run("Set nil in map", func(t *testing.T) { + m := map[string]interface{}{ + "ptr": stringPtr("test"), + "slice": []int{1, 2, 3}, + "map": map[string]string{"key": "value"}, + } + rfx := New(&m) + + rfx.Set("ptr", nil) + rfx.Set("slice", nil) + rfx.Set("map", nil) + + if m["ptr"] != nil { + t.Errorf("Expected ptr to be nil, got %v", m["ptr"]) + } + if m["slice"] != nil { + t.Errorf("Expected slice to be nil, got %v", m["slice"]) + } + if m["map"] != nil { + t.Errorf("Expected map to be nil, got %v", m["map"]) + } + }) + + t.Run("Set nil to various int types", func(t *testing.T) { + type Data struct { + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + } + data := Data{ + Int8: 127, + Int16: 32767, + Int32: 2147483647, + Int64: 9223372036854775807, + Uint8: 255, + Uint16: 65535, + Uint32: 4294967295, + Uint64: 18446744073709551615, + } + rfx := New(&data) + + rfx.Set("Int8", nil) + rfx.Set("Int16", nil) + rfx.Set("Int32", nil) + rfx.Set("Int64", nil) + rfx.Set("Uint8", nil) + rfx.Set("Uint16", nil) + rfx.Set("Uint32", nil) + rfx.Set("Uint64", nil) + + if data.Int8 != 0 { + t.Errorf("Expected Int8 to be 0, got %d", data.Int8) + } + if data.Int16 != 0 { + t.Errorf("Expected Int16 to be 0, got %d", data.Int16) + } + if data.Int32 != 0 { + t.Errorf("Expected Int32 to be 0, got %d", data.Int32) + } + if data.Int64 != 0 { + t.Errorf("Expected Int64 to be 0, got %d", data.Int64) + } + if data.Uint8 != 0 { + t.Errorf("Expected Uint8 to be 0, got %d", data.Uint8) + } + if data.Uint16 != 0 { + t.Errorf("Expected Uint16 to be 0, got %d", data.Uint16) + } + if data.Uint32 != 0 { + t.Errorf("Expected Uint32 to be 0, got %d", data.Uint32) + } + if data.Uint64 != 0 { + t.Errorf("Expected Uint64 to be 0, got %d", data.Uint64) + } + }) + + t.Run("Set nil to float types", func(t *testing.T) { + type Data struct { + Float32 float32 + Float64 float64 + } + data := Data{ + Float32: 3.14, + Float64: 2.718, + } + rfx := New(&data) + + rfx.Set("Float32", nil) + rfx.Set("Float64", nil) + + if data.Float32 != 0.0 { + t.Errorf("Expected Float32 to be 0.0, got %f", data.Float32) + } + if data.Float64 != 0.0 { + t.Errorf("Expected Float64 to be 0.0, got %f", data.Float64) + } + }) + + t.Run("Set nil to channel field", func(t *testing.T) { + type Data struct { + Chan chan int + } + ch := make(chan int) + data := Data{Chan: ch} + rfx := New(&data) + + rfx.Set("Chan", nil) + if data.Chan != nil { + t.Errorf("Expected Chan to be nil, got %v", data.Chan) + } + }) + + t.Run("Set nil to func field", func(t *testing.T) { + type Data struct { + Func func() + } + data := Data{Func: func() {}} + rfx := New(&data) + + rfx.Set("Func", nil) + if data.Func != nil { + t.Errorf("Expected Func to be nil") + } + }) + + t.Run("Set nil to nested struct with various types", func(t *testing.T) { + type Inner struct { + Ptr *string + Slice []int + Map map[string]string + Int int + String string + Bool bool + } + type Outer struct { + Inner Inner + } + data := Outer{ + Inner: Inner{ + Ptr: stringPtr("test"), + Slice: []int{1, 2, 3}, + Map: map[string]string{"key": "value"}, + Int: 42, + String: "hello", + Bool: true, + }, + } + rfx := New(&data) + + rfx.Set("Inner.Ptr", nil) + rfx.Set("Inner.Slice", nil) + rfx.Set("Inner.Map", nil) + rfx.Set("Inner.Int", nil) + rfx.Set("Inner.String", nil) + rfx.Set("Inner.Bool", nil) + + if data.Inner.Ptr != nil { + t.Errorf("Expected Inner.Ptr to be nil, got %v", data.Inner.Ptr) + } + if data.Inner.Slice != nil { + t.Errorf("Expected Inner.Slice to be nil, got %v", data.Inner.Slice) + } + if data.Inner.Map != nil { + t.Errorf("Expected Inner.Map to be nil, got %v", data.Inner.Map) + } + if data.Inner.Int != 0 { + t.Errorf("Expected Inner.Int to be 0, got %d", data.Inner.Int) + } + if data.Inner.String != "" { + t.Errorf("Expected Inner.String to be empty, got %s", data.Inner.String) + } + if data.Inner.Bool != false { + t.Errorf("Expected Inner.Bool to be false, got %v", data.Inner.Bool) + } + }) +} + +// Helper function to create string pointer +func stringPtr(s string) *string { + return &s +}