feat: 支持嵌套 R 和 valuex.Accessor 对象的路径访问
- 添加 unwrapAccessor 统一处理 Accessor 解引用 - 添加 deref/derefWithAccessor/derefValue 封装重复的解引用逻辑 - getValueByPath 支持穿透 R/Accessor 对象访问嵌套路径 - setNestedValue 支持在 R/Accessor 对象中设置嵌套值 - CloneValue 和 cloneElement 正确克隆 Accessor 底层值 - 支持在 map/struct/slice 中嵌套 R 对象并通过路径访问 - 添加完整测试覆盖 map/struct/slice/深度嵌套等场景 - 减少 38 行重复代码,提高代码可维护性
This commit is contained in:
parent
c62b07a119
commit
d233fc319b
30
rfx.go
30
rfx.go
@ -77,14 +77,12 @@ func (r *rfx) Append(items ...any) R {
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := r.getParentValue()
|
target := r.getParentValue()
|
||||||
for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface {
|
target = deref(target)
|
||||||
if target.IsNil() {
|
|
||||||
panic(ErrAppendNilValue)
|
|
||||||
}
|
|
||||||
target = target.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !target.IsValid() || target.Kind() != reflect.Slice {
|
if !target.IsValid() {
|
||||||
|
panic(ErrAppendNilValue)
|
||||||
|
}
|
||||||
|
if target.Kind() != reflect.Slice {
|
||||||
panic(ErrAppendNotSupported)
|
panic(ErrAppendNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,15 +104,9 @@ func (r *rfx) Append(items ...any) R {
|
|||||||
// setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct
|
// setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct
|
||||||
func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) error {
|
func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) error {
|
||||||
// 解引用指针和接口
|
// 解引用指针和接口
|
||||||
for current.Kind() == reflect.Ptr || current.Kind() == reflect.Interface {
|
current = derefWithAccessor(current)
|
||||||
if current.IsNil() {
|
|
||||||
return ErrNilPointerInPath
|
|
||||||
}
|
|
||||||
current = current.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !current.IsValid() {
|
if !current.IsValid() {
|
||||||
return ErrInvalidValueInPath
|
return ErrNilPointerInPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果只剩一个键,直接设置
|
// 如果只剩一个键,直接设置
|
||||||
@ -202,11 +194,9 @@ func (r *rfx) getParentValue(p ...string) reflect.Value {
|
|||||||
// setFieldValue 设置字段值的辅助方法
|
// setFieldValue 设置字段值的辅助方法
|
||||||
// 返回 error 包含详细的设置失败信息
|
// 返回 error 包含详细的设置失败信息
|
||||||
func (r *rfx) setFieldValue(target reflect.Value, key string, v any) error {
|
func (r *rfx) setFieldValue(target reflect.Value, key string, v any) error {
|
||||||
for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface {
|
target = deref(target)
|
||||||
if target.IsNil() {
|
if !target.IsValid() {
|
||||||
return ErrTargetNilPointer
|
return ErrTargetNilPointer
|
||||||
}
|
|
||||||
target = target.Elem()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch target.Kind() {
|
switch target.Kind() {
|
||||||
|
|||||||
189
rfx_nested_r_test.go
Normal file
189
rfx_nested_r_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
84
util.go
84
util.go
@ -13,12 +13,7 @@ import (
|
|||||||
// DeepClone 深度克隆一个 reflect.Value
|
// DeepClone 深度克隆一个 reflect.Value
|
||||||
func DeepClone(v reflect.Value) reflect.Value {
|
func DeepClone(v reflect.Value) reflect.Value {
|
||||||
// 解引用指针和接口以获取实际值
|
// 解引用指针和接口以获取实际值
|
||||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
v = deref(v)
|
||||||
if v.IsNil() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return reflect.Value{}
|
return reflect.Value{}
|
||||||
@ -48,6 +43,19 @@ func CloneValue(dst, src reflect.Value) {
|
|||||||
if src.IsNil() {
|
if src.IsNil() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解引用 Accessor,克隆其底层值
|
||||||
|
unwrapped := unwrapAccessor(src)
|
||||||
|
if unwrapped != src {
|
||||||
|
// 是 Accessor,克隆底层值
|
||||||
|
cloned := DeepClone(unwrapped)
|
||||||
|
if cloned.IsValid() {
|
||||||
|
dst.Set(cloned)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不是 Accessor,正常克隆
|
||||||
cloned := DeepClone(src.Elem())
|
cloned := DeepClone(src.Elem())
|
||||||
if cloned.IsValid() {
|
if cloned.IsValid() {
|
||||||
dst.Set(cloned)
|
dst.Set(cloned)
|
||||||
@ -104,6 +112,13 @@ func cloneSequence(dst, src reflect.Value) {
|
|||||||
func cloneElement(elem reflect.Value) reflect.Value {
|
func cloneElement(elem reflect.Value) reflect.Value {
|
||||||
// 首先检查 elem 自身是否是 interface
|
// 首先检查 elem 自身是否是 interface
|
||||||
if elem.Kind() == reflect.Interface && !elem.IsNil() {
|
if elem.Kind() == reflect.Interface && !elem.IsNil() {
|
||||||
|
// 检查并解引用 Accessor
|
||||||
|
unwrapped := unwrapAccessor(elem)
|
||||||
|
if unwrapped != elem {
|
||||||
|
// 是 Accessor,克隆底层值
|
||||||
|
return DeepClone(unwrapped)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 interface 包装的实际值
|
// 获取 interface 包装的实际值
|
||||||
wrapped := elem.Elem()
|
wrapped := elem.Elem()
|
||||||
|
|
||||||
@ -173,6 +188,49 @@ func needsDeepClone(kind reflect.Kind) bool {
|
|||||||
kind == reflect.Array
|
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 展开路径,支持点号分割
|
||||||
// 例如: expandPath("a.b", "c") -> []string{"a", "b", "c"}
|
// 例如: expandPath("a.b", "c") -> []string{"a", "b", "c"}
|
||||||
func expandPath(p ...string) []string {
|
func expandPath(p ...string) []string {
|
||||||
@ -194,12 +252,7 @@ func expandPath(p ...string) []string {
|
|||||||
// 1. 多个参数: Get("Address", "City")
|
// 1. 多个参数: Get("Address", "City")
|
||||||
// 2. 点号分割: Get("Address.City") 或混合使用 Get("Address.City", "ZipCode")
|
// 2. 点号分割: Get("Address.City") 或混合使用 Get("Address.City", "ZipCode")
|
||||||
func getValueByPath(v reflect.Value, p ...string) reflect.Value {
|
func getValueByPath(v reflect.Value, p ...string) reflect.Value {
|
||||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
v = derefWithAccessor(v)
|
||||||
if v.IsNil() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 展开所有路径片段,支持点号分割
|
// 展开所有路径片段,支持点号分割
|
||||||
keys := expandPath(p...)
|
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) {
|
v = derefWithAccessor(v)
|
||||||
if v.IsNil() {
|
|
||||||
return reflect.Value{}
|
|
||||||
}
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user