diff --git a/README.md b/README.md index d7a02f3..f145b1d 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,46 @@ rfx.Set("NewMap.key", "value") // 如果 NewMap 是 nil,会自动初始化 **注意**: 如果设置失败(路径不存在、类型不匹配等),会 panic。 +#### 切片追加 (Append) + +`Append` 用于在当前 `R` 对应的切片上追加一个或多个元素,并支持自动类型转换: + +```go +// 追加到顶层切片 +items := []string{"apple", "banana"} +rfx := reflux.New(&items) + +// 追加单个和多个元素 +rfx.Append("cherry") +rfx.Append("durian", "kiwi") +// items == []string{"apple", "banana", "cherry", "durian", "kiwi"} + +// 使用索引 -1 在切片前面插入新元素 +// 相当于在开头插入,原有元素整体后移 +rfx.Set("-1", "first") +// 对于上面的 items,现在结果为: +// items == []string{"first", "apple", "banana", "cherry", "durian", "kiwi"} + +// 追加时支持类型转换 +nums := []int{1, 2} +rfxNums := reflux.New(&nums) +rfxNums.Append("3", 4.0) // "3" -> 3, 4.0 -> 4 +// nums == []int{1, 2, 3, 4} + +// 追加到嵌套切片字段 +type Container struct { + Tags []string +} +c := Container{Tags: []string{"go"}} +rfxContainer := reflux.New(&c) +rfxContainer.Get("Tags").Append("rust", "python") +// c.Tags == []string{"go", "rust", "python"} +``` + +**注意**: +- 只能对**切片类型**调用 `Append`,否则会 panic +- 追加多个值时会一次性追加,避免多次扩容 + ### 5. 删除值 (Delete) `Delete` 方法支持链式调用,返回 Reflux 自身: @@ -774,4 +814,4 @@ type Reflux interface { 8. **大小写不敏感**: 支持使用小写字段名访问结构体字段 9. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal() 10. **并发安全**: Reflux 本身不是并发安全的,需要外部同步 -11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确 \ No newline at end of file +11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确 diff --git a/reflux.go b/reflux.go index 13bd6d1..d7a0f02 100644 --- a/reflux.go +++ b/reflux.go @@ -18,6 +18,11 @@ type R interface { // 示例: rfx.Set("name", "Alice").Set("age", 30) Set(key string, v any) R + // Append 追加指定路径的值 + // 参数 items 为要追加的值 + // 返回当前 R 实例以支持链式调用 + Append(items ...any) R + // Delete 删除指定路径的值 // 参数 p 为路径片段 // 返回当前 R 实例以支持链式调用 @@ -123,7 +128,7 @@ type R interface { // - 如果传入指针: 将直接使用该指针,可以修改原始数据 // - 如果传入值: 会自动创建一个深度克隆的指针副本,修改不影响原始数据 // 支持的类型: map、struct、slice、array -// 也支持 interface 类型,会自动解析到实际类型 +// 也支持 interface 类型以及部分基础类型(string/bool/float),会自动解析到实际类型 // 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对 func New(v any) R { rv := reflect.ValueOf(v) @@ -150,7 +155,14 @@ func New(v any) R { // 检查最终的实际类型是否为支持的类型 switch actualValue.Kind() { - case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array: + case reflect.Map, + reflect.Struct, + reflect.Slice, + reflect.Array, + reflect.String, + reflect.Bool, + reflect.Float32, + reflect.Float64: // 支持的类型 default: panic("rfx: unsupported type, only map, struct, slice, and array are allowed") diff --git a/rfx.go b/rfx.go index a5240a7..2087080 100644 --- a/rfx.go +++ b/rfx.go @@ -115,6 +115,42 @@ func (r *rfx) Set(key string, v any) R { return r } +// Append 追加指定路径的值 +// 参数 items 为要追加的值 +// 返回当前 R 实例以支持链式调用 +func (r *rfx) Append(items ...any) R { + // 没有要追加的元素,直接返回 + if len(items) == 0 { + return r + } + + target := r.getParentValue() + for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface { + if target.IsNil() { + panic("rfx: cannot append to nil value") + } + target = target.Elem() + } + + if !target.IsValid() || target.Kind() != reflect.Slice { + panic("rfx: Append only supports slice type") + } + + // 一次性构造所有要追加的元素,然后调用一次 reflect.Append + elemType := target.Type().Elem() + newValues := make([]reflect.Value, len(items)) + for i, item := range items { + newElem := reflect.New(elemType).Elem() + if !r.setValue(newElem, item) { + panic("rfx: failed to append item to slice") + } + newValues[i] = newElem + } + + target.Set(reflect.Append(target, newValues...)) + return r +} + // setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) bool { // 解引用指针和接口 @@ -232,7 +268,36 @@ func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool { } target.SetMapIndex(reflect.ValueOf(key), val) return true - case reflect.Slice, reflect.Array: + case reflect.Slice: + idx, err := strconv.Atoi(key) + if err != nil { + return false + } + + // 对于切片,支持使用索引 -1 追加新元素(插入到切片前面) + if idx == -1 { + elemType := target.Type().Elem() + newElem := reflect.New(elemType).Elem() + if !r.setValue(newElem, v) { + return false + } + // 将新元素放在前面,原有元素顺序后移 + newSlice := reflect.MakeSlice(target.Type(), 0, target.Len()+1) + newSlice = reflect.Append(newSlice, newElem) + newSlice = reflect.AppendSlice(newSlice, target) + target.Set(newSlice) + return true + } + + if idx < 0 || idx >= target.Len() { + return false + } + elem := target.Index(idx) + if !elem.CanSet() { + return false + } + return r.setValue(elem, v) + case reflect.Array: idx, err := strconv.Atoi(key) if err != nil || idx < 0 || idx >= target.Len() { return false diff --git a/rfx_example_test.go b/rfx_example_test.go index 550caa7..30100f4 100644 --- a/rfx_example_test.go +++ b/rfx_example_test.go @@ -57,11 +57,17 @@ func ExampleNew_withPointer_slice() { rfx.Set("0", "orange") rfx.Set("2", "grape") + // 使用 Append 追加元素 + rfx.Append("kiwi") + + // 使用索引 -1 在切片前面插入新元素 + rfx.Set("-1", "pear") + // 原始 slice 已被修改 fmt.Printf("Items: %v\n", items) // Output: - // Items: [orange banana grape] + // Items: [pear orange banana grape kiwi] } // ExampleNew_withValue 演示如何使用 New 函数传入值 @@ -158,19 +164,27 @@ func ExampleNew_withValue_slice() { rfx.Set("0", "orange") rfx.Set("2", "grape") + // 使用 Append 追加元素 + rfx.Append("kiwi") + + // 使用索引 -1 在切片前面插入新元素 + rfx.Set("-1", "pear") + // 原始 slice 未被修改 fmt.Printf("Original: %v\n", items) // rfx 内部的数据已修改 - fmt.Printf("Clone: [%s %s %s]\n", + fmt.Printf("Clone: [%s %s %s %s %s]\n", rfx.Get("0").String(), rfx.Get("1").String(), - rfx.Get("2").String()) + rfx.Get("2").String(), + rfx.Get("3").String(), + rfx.Get("4").String()) // Output: // After New: [apple banana cherry] // Original: [apple banana cherry] - // Clone: [orange banana grape] + // Clone: [pear orange banana grape kiwi] } // ExampleNew_withValue_nestedStruct 演示传入嵌套结构体值