diff --git a/rfx.go b/rfx.go index 90cc6c0..a0ec788 100644 --- a/rfx.go +++ b/rfx.go @@ -77,14 +77,12 @@ func (r *rfx) Append(items ...any) R { } target := r.getParentValue() - for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface { - if target.IsNil() { - panic(ErrAppendNilValue) - } - target = target.Elem() - } + target = deref(target) - if !target.IsValid() || target.Kind() != reflect.Slice { + if !target.IsValid() { + panic(ErrAppendNilValue) + } + if target.Kind() != reflect.Slice { panic(ErrAppendNotSupported) } @@ -106,15 +104,9 @@ func (r *rfx) Append(items ...any) R { // setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) error { // 解引用指针和接口 - for current.Kind() == reflect.Ptr || current.Kind() == reflect.Interface { - if current.IsNil() { - return ErrNilPointerInPath - } - current = current.Elem() - } - + current = derefWithAccessor(current) if !current.IsValid() { - return ErrInvalidValueInPath + return ErrNilPointerInPath } // 如果只剩一个键,直接设置 @@ -202,11 +194,9 @@ func (r *rfx) getParentValue(p ...string) reflect.Value { // setFieldValue 设置字段值的辅助方法 // 返回 error 包含详细的设置失败信息 func (r *rfx) setFieldValue(target reflect.Value, key string, v any) error { - for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface { - if target.IsNil() { - return ErrTargetNilPointer - } - target = target.Elem() + target = deref(target) + if !target.IsValid() { + return ErrTargetNilPointer } switch target.Kind() { diff --git a/rfx_nested_r_test.go b/rfx_nested_r_test.go new file mode 100644 index 0000000..0bf278e --- /dev/null +++ b/rfx_nested_r_test.go @@ -0,0 +1,189 @@ +package reflux + +import ( + "testing" +) + +// TestNestedRInMap 测试在 map 中嵌套 R 对象的情况 +func TestNestedRInMap(t *testing.T) { + // 创建一个嵌套的 R 对象 + inner := New(map[string]any{ + "X": map[string]any{ + "XX": "value", + }, + }) + + // 将 R 对象嵌入到 map 中 + outer := New(map[string]any{ + "A": inner, + }) + + // 尝试通过路径访问 + result := outer.Get("A", "X", "XX").String() + if result != "value" { + t.Errorf("Expected 'value', got '%s'", result) + } + + // 尝试通过点号路径访问 + result2 := outer.Get("A.X.XX").String() + if result2 != "value" { + t.Errorf("Expected 'value', got '%s'", result2) + } +} + +// TestNestedRSetInMap 测试在包含 R 对象的 map 中设置值 +func TestNestedRSetInMap(t *testing.T) { + inner := New(map[string]any{ + "X": map[string]any{ + "XX": "old_value", + }, + }) + + outer := New(map[string]any{ + "A": inner, + }) + + // 尝试通过路径设置值 + outer.Set("A.X.XX", "new_value") + + result := outer.Get("A.X.XX").String() + if result != "new_value" { + t.Errorf("Expected 'new_value', got '%s'", result) + } +} + +// TestNestedRInStruct 测试在 struct 中嵌套 R 对象 +func TestNestedRInStruct(t *testing.T) { + type Container struct { + Data R + } + + inner := New(map[string]any{ + "Name": "Alice", + "Age": 30, + }) + + container := Container{Data: inner} + outer := New(&container) + + // 通过路径访问 + name := outer.Get("Data", "Name").String() + if name != "Alice" { + t.Errorf("Expected 'Alice', got '%s'", name) + } + + // 通过点号路径访问 + age := outer.Get("Data.Age").Int() + if age != 30 { + t.Errorf("Expected 30, got %d", age) + } + + // 设置值 + outer.Set("Data.Name", "Bob") + newName := outer.Get("Data.Name").String() + if newName != "Bob" { + t.Errorf("Expected 'Bob', got '%s'", newName) + } +} + +// TestNestedRInSlice 测试在 slice 中嵌套 R 对象 +func TestNestedRInSlice(t *testing.T) { + r1 := New(map[string]any{"id": 1, "name": "Alice"}) + r2 := New(map[string]any{"id": 2, "name": "Bob"}) + + outer := New(map[string]any{ + "items": []any{r1, r2}, + }) + + // 访问 slice 中的 R 对象 + name1 := outer.Get("items", "0", "name").String() + if name1 != "Alice" { + t.Errorf("Expected 'Alice', got '%s'", name1) + } + + name2 := outer.Get("items.1.name").String() + if name2 != "Bob" { + t.Errorf("Expected 'Bob', got '%s'", name2) + } + + // 设置值 + outer.Set("items.0.name", "Charlie") + newName := outer.Get("items.0.name").String() + if newName != "Charlie" { + t.Errorf("Expected 'Charlie', got '%s'", newName) + } +} + +// TestDeepNestedR 测试深度嵌套的 R 对象 +func TestDeepNestedR(t *testing.T) { + level3 := New(map[string]any{ + "value": "deep", + }) + + level2 := New(map[string]any{ + "level3": level3, + }) + + level1 := New(map[string]any{ + "level2": level2, + }) + + outer := New(map[string]any{ + "level1": level1, + }) + + // 深度路径访问 + value := outer.Get("level1", "level2", "level3", "value").String() + if value != "deep" { + t.Errorf("Expected 'deep', got '%s'", value) + } + + // 深度路径设置 + outer.Set("level1.level2.level3.value", "updated") + newValue := outer.Get("level1.level2.level3.value").String() + if newValue != "updated" { + t.Errorf("Expected 'updated', got '%s'", newValue) + } +} + +// TestNestedRExists 测试 Exists 方法对嵌套 R 对象的支持 +func TestNestedRExists(t *testing.T) { + inner := New(map[string]any{ + "X": map[string]any{ + "XX": "value", + }, + }) + + outer := New(map[string]any{ + "A": inner, + }) + + if !outer.Exists("A", "X", "XX") { + t.Error("Expected A.X.XX to exist") + } + + if outer.Exists("A", "X", "YY") { + t.Error("Expected A.X.YY to not exist") + } +} + +// TestNestedRArray 测试 Array 方法 +func TestNestedRArray(t *testing.T) { + r1 := New(map[string]any{"id": 1}) + r2 := New(map[string]any{"id": 2}) + + outer := New(map[string]any{ + "items": []any{r1, r2}, + }) + + items := outer.Get("items").Array() + if len(items) != 2 { + t.Errorf("Expected 2 items, got %d", len(items)) + } + + // 验证数组元素可以访问 + id1 := items[0].Get("id").Int() + if id1 != 1 { + t.Errorf("Expected id 1, got %d", id1) + } +} diff --git a/util.go b/util.go index 8797a50..027d4ef 100644 --- a/util.go +++ b/util.go @@ -13,12 +13,7 @@ import ( // DeepClone 深度克隆一个 reflect.Value func DeepClone(v reflect.Value) reflect.Value { // 解引用指针和接口以获取实际值 - for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { - if v.IsNil() { - return reflect.Value{} - } - v = v.Elem() - } + v = deref(v) if !v.IsValid() { return reflect.Value{} @@ -48,6 +43,19 @@ func CloneValue(dst, src reflect.Value) { if src.IsNil() { return } + + // 解引用 Accessor,克隆其底层值 + unwrapped := unwrapAccessor(src) + if unwrapped != src { + // 是 Accessor,克隆底层值 + cloned := DeepClone(unwrapped) + if cloned.IsValid() { + dst.Set(cloned) + } + return + } + + // 不是 Accessor,正常克隆 cloned := DeepClone(src.Elem()) if cloned.IsValid() { dst.Set(cloned) @@ -104,6 +112,13 @@ func cloneSequence(dst, src reflect.Value) { func cloneElement(elem reflect.Value) reflect.Value { // 首先检查 elem 自身是否是 interface if elem.Kind() == reflect.Interface && !elem.IsNil() { + // 检查并解引用 Accessor + unwrapped := unwrapAccessor(elem) + if unwrapped != elem { + // 是 Accessor,克隆底层值 + return DeepClone(unwrapped) + } + // 获取 interface 包装的实际值 wrapped := elem.Elem() @@ -173,6 +188,49 @@ func needsDeepClone(kind reflect.Kind) bool { kind == reflect.Array } +// unwrapAccessor 解引用 valuex.Accessor (包括 R),返回其底层值 +// 如果 v 不是 interface 或不包含 Accessor,返回原值 +func unwrapAccessor(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + if accessor, ok := v.Interface().(valuex.Accessor); ok { + rawVal := accessor.Raw() + if rawVal.IsValid() { + return rawVal + } + } + } + return v +} + +// derefValue 递归解引用指针和接口,返回实际值 +// nilValue: 遇到 nil 时返回的值 +// supportAccessor: 是否支持解引用 Accessor +func derefValue(v reflect.Value, nilValue reflect.Value, supportAccessor bool) reflect.Value { + for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + if v.IsNil() { + return nilValue + } + if supportAccessor { + v = unwrapAccessor(v) + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + break + } + } + v = v.Elem() + } + return v +} + +// deref 递归解引用指针和接口,返回实际值(nil 时返回无效值) +func deref(v reflect.Value) reflect.Value { + return derefValue(v, reflect.Value{}, false) +} + +// derefWithAccessor 递归解引用指针和接口(支持 Accessor),返回实际值(nil 时返回无效值) +func derefWithAccessor(v reflect.Value) reflect.Value { + return derefValue(v, reflect.Value{}, true) +} + // expandPath 展开路径,支持点号分割 // 例如: expandPath("a.b", "c") -> []string{"a", "b", "c"} func expandPath(p ...string) []string { @@ -194,12 +252,7 @@ func expandPath(p ...string) []string { // 1. 多个参数: Get("Address", "City") // 2. 点号分割: Get("Address.City") 或混合使用 Get("Address.City", "ZipCode") func getValueByPath(v reflect.Value, p ...string) reflect.Value { - for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { - if v.IsNil() { - return reflect.Value{} - } - v = v.Elem() - } + v = derefWithAccessor(v) // 展开所有路径片段,支持点号分割 keys := expandPath(p...) @@ -227,12 +280,7 @@ func getValueByPath(v reflect.Value, p ...string) reflect.Value { } // 解引用指针和接口 - for v.IsValid() && (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) { - if v.IsNil() { - return reflect.Value{} - } - v = v.Elem() - } + v = derefWithAccessor(v) } return v