feat: 支持切片追加及 -1 头部插入

This commit is contained in:
what 2025-12-04 11:28:18 +08:00
parent cbe079ddcd
commit b373dde7f7
4 changed files with 139 additions and 8 deletions

View File

@ -246,6 +246,46 @@ rfx.Set("NewMap.key", "value") // 如果 NewMap 是 nil,会自动初始化
**注意**: 如果设置失败(路径不存在、类型不匹配等),会 panic。 **注意**: 如果设置失败(路径不存在、类型不匹配等),会 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) ### 5. 删除值 (Delete)
`Delete` 方法支持链式调用,返回 Reflux 自身: `Delete` 方法支持链式调用,返回 Reflux 自身:
@ -774,4 +814,4 @@ type Reflux interface {
8. **大小写不敏感**: 支持使用小写字段名访问结构体字段 8. **大小写不敏感**: 支持使用小写字段名访问结构体字段
9. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal() 9. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal()
10. **并发安全**: Reflux 本身不是并发安全的,需要外部同步 10. **并发安全**: Reflux 本身不是并发安全的,需要外部同步
11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确 11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确

View File

@ -18,6 +18,11 @@ type R interface {
// 示例: rfx.Set("name", "Alice").Set("age", 30) // 示例: rfx.Set("name", "Alice").Set("age", 30)
Set(key string, v any) R Set(key string, v any) R
// Append 追加指定路径的值
// 参数 items 为要追加的值
// 返回当前 R 实例以支持链式调用
Append(items ...any) R
// Delete 删除指定路径的值 // Delete 删除指定路径的值
// 参数 p 为路径片段 // 参数 p 为路径片段
// 返回当前 R 实例以支持链式调用 // 返回当前 R 实例以支持链式调用
@ -123,7 +128,7 @@ type R interface {
// - 如果传入指针: 将直接使用该指针,可以修改原始数据 // - 如果传入指针: 将直接使用该指针,可以修改原始数据
// - 如果传入值: 会自动创建一个深度克隆的指针副本,修改不影响原始数据 // - 如果传入值: 会自动创建一个深度克隆的指针副本,修改不影响原始数据
// 支持的类型: map、struct、slice、array // 支持的类型: map、struct、slice、array
// 也支持 interface 类型,会自动解析到实际类型 // 也支持 interface 类型以及部分基础类型(string/bool/float),会自动解析到实际类型
// 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对 // 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对
func New(v any) R { func New(v any) R {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
@ -150,7 +155,14 @@ func New(v any) R {
// 检查最终的实际类型是否为支持的类型 // 检查最终的实际类型是否为支持的类型
switch actualValue.Kind() { 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: default:
panic("rfx: unsupported type, only map, struct, slice, and array are allowed") panic("rfx: unsupported type, only map, struct, slice, and array are allowed")

67
rfx.go
View File

@ -115,6 +115,42 @@ func (r *rfx) Set(key string, v any) R {
return 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 // setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct
func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) bool { 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) target.SetMapIndex(reflect.ValueOf(key), val)
return true 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) idx, err := strconv.Atoi(key)
if err != nil || idx < 0 || idx >= target.Len() { if err != nil || idx < 0 || idx >= target.Len() {
return false return false

View File

@ -57,11 +57,17 @@ func ExampleNew_withPointer_slice() {
rfx.Set("0", "orange") rfx.Set("0", "orange")
rfx.Set("2", "grape") rfx.Set("2", "grape")
// 使用 Append 追加元素
rfx.Append("kiwi")
// 使用索引 -1 在切片前面插入新元素
rfx.Set("-1", "pear")
// 原始 slice 已被修改 // 原始 slice 已被修改
fmt.Printf("Items: %v\n", items) fmt.Printf("Items: %v\n", items)
// Output: // Output:
// Items: [orange banana grape] // Items: [pear orange banana grape kiwi]
} }
// ExampleNew_withValue 演示如何使用 New 函数传入值 // ExampleNew_withValue 演示如何使用 New 函数传入值
@ -158,19 +164,27 @@ func ExampleNew_withValue_slice() {
rfx.Set("0", "orange") rfx.Set("0", "orange")
rfx.Set("2", "grape") rfx.Set("2", "grape")
// 使用 Append 追加元素
rfx.Append("kiwi")
// 使用索引 -1 在切片前面插入新元素
rfx.Set("-1", "pear")
// 原始 slice 未被修改 // 原始 slice 未被修改
fmt.Printf("Original: %v\n", items) fmt.Printf("Original: %v\n", items)
// rfx 内部的数据已修改 // rfx 内部的数据已修改
fmt.Printf("Clone: [%s %s %s]\n", fmt.Printf("Clone: [%s %s %s %s %s]\n",
rfx.Get("0").String(), rfx.Get("0").String(),
rfx.Get("1").String(), rfx.Get("1").String(),
rfx.Get("2").String()) rfx.Get("2").String(),
rfx.Get("3").String(),
rfx.Get("4").String())
// Output: // Output:
// After New: [apple banana cherry] // After New: [apple banana cherry]
// Original: [apple banana cherry] // Original: [apple banana cherry]
// Clone: [orange banana grape] // Clone: [pear orange banana grape kiwi]
} }
// ExampleNew_withValue_nestedStruct 演示传入嵌套结构体值 // ExampleNew_withValue_nestedStruct 演示传入嵌套结构体值