feat: 完善 New 方法支持指针和值参数,新增 interface 类型支持

- 支持指针模式: New(&data) - 直接修改原始数据
- 支持值模式: New(data) - 创建深度克隆,不影响原始数据
- 新增 interface{} 类型支持,自动解析到实际类型
- 对 map 和 slice 等引用类型也进行完全深度克隆

- 新增 9 个单元测试覆盖指针/值/interface 场景
- 新增 9 个示例测试展示各种用法
- 所有测试通过,保持性能

- 更新 README.md 详细说明指针/值传递的区别
- 新增 interface 类型使用示例
- 新增泛型数据处理使用场景
- 更新注意事项说明

- 使用递归解引用处理 interface 和指针类型
- 利用 DeepClone 确保引用类型的完全独立
- 保持向后兼容,现有代码无需修改
This commit is contained in:
what 2025-12-02 21:08:13 +08:00
parent d9f178020a
commit 97d41fa6d8
4 changed files with 520 additions and 24 deletions

124
README.md
View File

@ -9,7 +9,9 @@ Reflux 是一个 Go 语言包,提供了统一的接口用于访问和操作嵌
- 🔄 **类型转换**: 基于 [spf13/cast](https://github.com/spf13/cast) 的强大类型转换支持 - 🔄 **类型转换**: 基于 [spf13/cast](https://github.com/spf13/cast) 的强大类型转换支持
- ✏️ **修改数据**: 支持通过路径设置和删除值 - ✏️ **修改数据**: 支持通过路径设置和删除值
- 🔗 **链式调用**: Set 和 Delete 方法支持链式调用,如 `rfx.Set("name", "Alice").Delete("age").Set("city", "Beijing")` - 🔗 **链式调用**: Set 和 Delete 方法支持链式调用,如 `rfx.Set("name", "Alice").Delete("age").Set("city", "Beijing")`
- 📋 **深度克隆**: Scope 方法创建深度克隆,修改不影响原始数据 - 📋 **深度克隆**: Scope 方法和值传递模式创建深度克隆,修改不影响原始数据
- 🎭 **灵活模式**: 支持指针和值两种传递方式,提供不同的数据操作语义
- 🔌 **Interface 支持**: 自动解析 `interface{}` 类型到实际类型
- 🔤 **大小写不敏感**: 支持使用小写字段名访问和修改结构体字段 - 🔤 **大小写不敏感**: 支持使用小写字段名访问和修改结构体字段
- 🔑 **键名提取**: Keys 方法可获取结构体或 Map 的所有键名 - 🔑 **键名提取**: Keys 方法可获取结构体或 Map 的所有键名
- 📝 **JSON 集成**: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化 - 📝 **JSON 集成**: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化
@ -68,12 +70,62 @@ func main() {
### 1. 创建 Reflux ### 1. 创建 Reflux
Reflux 支持**指针**和**值**两种传递方式,提供不同的数据操作语义:
#### 指针模式 - 直接修改原始数据
```go ```go
user := User{Name: "Alice"} user := User{Name: "Alice"}
rfx := reflux.New(&user) // 传入指针以支持修改 rfx := reflux.New(&user) // 传入指针
rfx.Set("Name", "Bob")
// user.Name 已被修改为 "Bob"
``` ```
**重要**: `New()` 函数必须传入指针类型,否则会 panic。 #### 值模式 - 深度克隆,不影响原始数据
```go
user := User{Name: "Alice"}
rfx := reflux.New(user) // 传入值
rfx.Set("Name", "Bob")
// user.Name 仍然是 "Alice"
// rfx 内部是独立的深度克隆
```
#### Interface 类型支持
Reflux 支持 `interface{}` (或 `any`) 类型,会自动解析到实际类型:
```go
// interface{} 包装的值 - 创建深度克隆
var config any = map[string]string{"host": "localhost"}
rfx := reflux.New(config)
rfx.Set("host", "127.0.0.1")
// 原始 config 不受影响
// interface{} 包装的指针 - 直接修改
var data any = &config
rfx := reflux.New(data)
rfx.Set("host", "127.0.0.1")
// config 已被修改
```
#### 支持的类型
| 类型 | 说明 | 示例 |
|-----|------|------|
| **Struct** | 结构体 | `New(Person{})``New(&person)` |
| **Map** | 映射 | `New(map[string]any{})``New(&m)` |
| **Slice** | 切片 | `New([]string{})``New(&s)` |
| **Array** | 数组 | `New([3]int{})``New(&a)` |
| **Interface** | 接口 | `New(anyValue)` - 自动解析 |
**重要说明**:
- ✅ **指针模式**: 传入指针(如 `&user`)时,所有修改都会**直接影响原始数据**
- ✅ **值模式**: 传入值(如 `user`)时,会创建**深度克隆**,修改不影响原始数据
- ✅ **深度克隆**: 对于 map 和 slice 等引用类型,值模式也会创建完全独立的副本
- ❌ **不支持**: 基本类型(int, string, bool)、chan、func 等不是容器的类型
### 2. 获取值 (Get) ### 2. 获取值 (Get)
@ -581,6 +633,48 @@ func main() {
- **插件热加载**: 更新插件配置而无需重启 - **插件热加载**: 更新插件配置而无需重启
- **API 部分响应**: 只处理 API 返回的部分字段 - **API 部分响应**: 只处理 API 返回的部分字段
### 7. 泛型数据处理 (Interface 类型)
使用 interface{} 类型处理未知类型的数据:
```go
// 函数返回 any 类型
func loadFromAPI() any {
// 可能返回 map 或 struct
return map[string]any{
"user": map[string]any{
"name": "Alice",
"age": 30,
},
}
}
func processData(data any) {
// 自动解析 interface{} 到实际类型
rfx := reflux.New(data) // 值模式,创建深度克隆
// 安全地修改数据
rfx.Set("user.name", "Bob")
rfx.Set("user.age", 35)
// 原始数据不受影响
}
// 或者使用指针模式
func updateData(dataPtr any) {
// interface{} 包装指针,直接修改
rfx := reflux.New(dataPtr)
rfx.Set("user.name", "Charlie")
// 原始数据已被修改
}
```
使用场景:
- **插件系统**: 处理未知结构的插件配置
- **泛型配置**: 统一处理不同格式的配置数据
- **API 适配**: 适配多种 API 响应格式
- **数据转换**: 在不同数据结构间转换
## 路径语法 ## 路径语法
Reflux 支持灵活的点号路径语法: Reflux 支持灵活的点号路径语法:
@ -667,16 +761,20 @@ type Reflux interface {
## 注意事项 ## 注意事项
1. **指针传递**: `New()` 函数必须传递指针,否则会 panic 1. **指针 vs 值传递**:
2. **Scope 行为**: `Scope` 返回深度克隆,修改不会影响原始数据 - 传入**指针** (`New(&data)`) 时,修改会影响原始数据
3. **链式调用**: Set 和 Delete 方法都支持链式调用,返回 Reflux 自身 - 传入**值** (`New(data)`) 时,会创建深度克隆,修改不影响原始数据
4. **类型转换**: 使用 spf13/cast 进行类型转换,转换失败会 panic - 对于 map 和 slice 等引用类型,值模式也会完全克隆
5. **切片删除**: 删除切片元素会创建新切片并重新赋值 2. **Interface 支持**: 支持 `interface{}` 类型,会自动解析到实际类型并保持正确的指针/值语义
6. **Map 初始化**: 对 nil map 调用 Set 会自动初始化 map 3. **Scope 行为**: `Scope` 返回深度克隆,修改不会影响原始数据
7. **大小写不敏感**: 支持使用小写字段名访问结构体字段 4. **链式调用**: Set 和 Delete 方法都支持链式调用,返回 Reflux 自身
8. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal() 5. **类型转换**: 使用 spf13/cast 进行类型转换,转换失败会 panic
9. **并发安全**: Reflux 本身不是并发安全的,需要外部同步 6. **切片删除**: 删除切片元素会创建新切片并重新赋值
10. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确 7. **Map 初始化**: 对 nil map 调用 Set 会自动初始化 map
8. **大小写不敏感**: 支持使用小写字段名访问结构体字段
9. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal()
10. **并发安全**: Reflux 本身不是并发安全的,需要外部同步
11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确
## 示例代码 ## 示例代码

View File

@ -119,13 +119,49 @@ type R interface {
} }
// New 创建一个新的 R 实例 // New 创建一个新的 R 实例
// 参数 v 必须是指针类型,否则会 panic // 参数 v 可以是指针或非指针类型
// - 如果传入指针: 将直接使用该指针,可以修改原始数据
// - 如果传入值: 会自动创建一个深度克隆的指针副本,修改不影响原始数据
// 支持的类型: map、struct、slice、array
// 也支持 interface 类型,会自动解析到实际类型
// 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对 // 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对
func New(v any) R { func New(v any) R {
rv := reflect.ValueOf(v) rv := reflect.ValueOf(v)
// 如果传入的不是指针,panic
if rv.Kind() != reflect.Ptr { if !rv.IsValid() {
panic("structmap: New() requires a pointer argument") panic("rfx: invalid value")
} }
// 记录原始是否为指针
isPtr := rv.Kind() == reflect.Ptr
// 递归解引用指针和接口,直到获取实际的值类型
actualValue := rv
for actualValue.Kind() == reflect.Ptr || actualValue.Kind() == reflect.Interface {
if actualValue.IsNil() {
panic("rfx: cannot use nil pointer or nil interface")
}
actualValue = actualValue.Elem()
// 如果解引用后的类型是指针,标记为指针模式
if actualValue.Kind() == reflect.Ptr {
isPtr = true
}
}
// 检查最终的实际类型是否为支持的类型
switch actualValue.Kind() {
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
// 支持的类型
default:
panic("rfx: unsupported type, only map, struct, slice, and array are allowed")
}
// 如果原始传入的不是指针类型,需要进行深度克隆以避免修改原始数据
// 对于引用类型(map, slice)这尤其重要
if !isPtr {
// 使用深度克隆创建一个完全独立的副本
rv = DeepClone(actualValue)
}
return &rfx{value: rv} return &rfx{value: rv}
} }

View File

@ -2,6 +2,228 @@ package reflux
import "fmt" import "fmt"
// ExampleNew_withPointer 演示如何使用 New 函数传入指针
// 传入指针时,修改会影响原始数据
func ExampleNew_withPointer() {
type Person struct {
Name string
Age int
}
person := Person{Name: "Alice", Age: 30}
// 传入指针 - 修改会影响原始数据
rfx := New(&person)
rfx.Set("Name", "Bob")
rfx.Set("Age", 35)
// 原始数据已被修改
fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age)
// Output:
// Name: Bob, Age: 35
}
// ExampleNew_withPointer_map 演示传入 map 指针
func ExampleNew_withPointer_map() {
config := map[string]any{
"host": "localhost",
"port": 8080,
}
// 传入 map 指针 - 修改会影响原始数据
rfx := New(&config)
rfx.Set("host", "127.0.0.1")
rfx.Set("port", 9090)
// 原始 map 已被修改
fmt.Printf("Host: %s, Port: %v\n", config["host"], config["port"])
// Output:
// Host: 127.0.0.1, Port: 9090
}
// ExampleNew_withPointer_slice 演示传入 slice 指针
func ExampleNew_withPointer_slice() {
items := []string{"apple", "banana", "cherry"}
// 传入 slice 指针 - 修改会影响原始数据
rfx := New(&items)
rfx.Set("0", "orange")
rfx.Set("2", "grape")
// 原始 slice 已被修改
fmt.Printf("Items: %v\n", items)
// Output:
// Items: [orange banana grape]
}
// ExampleNew_withValue 演示如何使用 New 函数传入值
// 传入值时,会创建深度克隆,修改不会影响原始数据
func ExampleNew_withValue() {
type Person struct {
Name string
Age int
}
person := Person{Name: "Alice", Age: 30}
// 传入值 - 会创建深度克隆,修改不会影响原始数据
rfx := New(person)
rfx.Set("Name", "Bob")
rfx.Set("Age", 35)
// 原始数据未被修改
fmt.Printf("Original - Name: %s, Age: %d\n", person.Name, person.Age)
// rfx 内部的数据已修改
fmt.Printf("Clone - Name: %s, Age: %d\n",
rfx.Get("Name").String(),
rfx.Get("Age").Int())
// Output:
// Original - Name: Alice, Age: 30
// Clone - Name: Bob, Age: 35
}
// ExampleNew_withValue_map 演示传入 map 值
// 传入值时会创建深度克隆,即使 map 是引用类型
func ExampleNew_withValue_map() {
config := map[string]any{
"host": "localhost",
"port": 8080,
}
// 传入 map 值 - 会创建深度克隆
rfx := New(config)
rfx.Set("host", "127.0.0.1")
rfx.Set("port", 9090)
// 原始 map 未被修改
fmt.Printf("Original - Host: %s, Port: %v\n", config["host"], config["port"])
// rfx 内部的数据已修改
fmt.Printf("Clone - Host: %s, Port: %v\n",
rfx.Get("host").String(),
rfx.Get("port").Int())
// Output:
// Original - Host: localhost, Port: 8080
// Clone - Host: 127.0.0.1, Port: 9090
}
// ExampleNew_withValue_slice 演示传入 slice 值
// 传入值时会创建深度克隆,即使 slice 是引用类型
func ExampleNew_withValue_slice() {
items := []string{"apple", "banana", "cherry"}
// 传入 slice 值 - 会创建深度克隆
rfx := New(items)
rfx.Set("0", "orange")
rfx.Set("2", "grape")
// 原始 slice 未被修改
fmt.Printf("Original: %v\n", items)
// rfx 内部的数据已修改
fmt.Printf("Clone: [%s %s %s]\n",
rfx.Get("0").String(),
rfx.Get("1").String(),
rfx.Get("2").String())
// Output:
// Original: [apple banana cherry]
// Clone: [orange banana grape]
}
// ExampleNew_withValue_nestedStruct 演示传入嵌套结构体值
func ExampleNew_withValue_nestedStruct() {
type Address struct {
City string
Street string
}
type Person struct {
Name string
Address Address
}
person := Person{
Name: "Alice",
Address: Address{
City: "Beijing",
Street: "Main St",
},
}
// 传入值 - 会创建深度克隆,包括嵌套的结构体
rfx := New(person)
rfx.Set("Name", "Bob")
rfx.Set("Address.City", "Shanghai")
// 原始数据未被修改
fmt.Printf("Original - Name: %s, City: %s\n", person.Name, person.Address.City)
// rfx 内部的数据已修改
fmt.Printf("Clone - Name: %s, City: %s\n",
rfx.Get("Name").String(),
rfx.Get("Address", "City").String())
// Output:
// Original - Name: Alice, City: Beijing
// Clone - Name: Bob, City: Shanghai
}
// ExampleNew_withInterface 演示 interface{} 类型的支持
// 当数据被包装在 interface{} 中时,会自动解析到实际类型
func ExampleNew_withInterface() {
// interface{} 包装的 map
var config any = map[string]any{
"host": "localhost",
"port": 8080,
}
// 传入 interface{} 值 - 会创建深度克隆
rfx := New(config)
rfx.Set("host", "127.0.0.1")
rfx.Set("port", 9090)
// 原始数据未被修改
originalMap := config.(map[string]any)
fmt.Printf("Original - Host: %s, Port: %v\n", originalMap["host"], originalMap["port"])
// rfx 内部的数据已修改
fmt.Printf("Clone - Host: %s, Port: %v\n",
rfx.Get("host").String(),
rfx.Get("port").Int())
// Output:
// Original - Host: localhost, Port: 8080
// Clone - Host: 127.0.0.1, Port: 9090
}
// ExampleNew_withInterfacePointer 演示 interface{} 包装指针的情况
func ExampleNew_withInterfacePointer() {
type Config struct {
Host string
Port int
}
config := Config{Host: "localhost", Port: 8080}
// interface{} 包装指针 - 修改会影响原始数据
var data any = &config
rfx := New(data)
rfx.Set("Host", "127.0.0.1")
rfx.Set("Port", 9090)
// 原始数据已被修改(因为是指针)
fmt.Printf("Host: %s, Port: %d\n", config.Host, config.Port)
// Output:
// Host: 127.0.0.1, Port: 9090
}
// ExampleNew_withMap 演示如何使用 New 函数传入 map // ExampleNew_withMap 演示如何使用 New 函数传入 map
func ExampleNew_withMap() { func ExampleNew_withMap() {
// 创建一个 map // 创建一个 map

View File

@ -23,15 +23,155 @@ type Address struct {
// TestNew 测试 New 函数 // TestNew 测试 New 函数
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
t.Run("New with pointer", func(t *testing.T) {
// 测试传入指针 - 修改应该影响原始数据
p := Person{Name: "Alice", Age: 30} p := Person{Name: "Alice", Age: 30}
rfx := New(&p)
// 测试传入非指针值应该 panic rfx.Set("Name", "Bob")
if p.Name != "Bob" {
t.Errorf("Expected original value to be modified, got '%s'", p.Name)
}
})
t.Run("New with value", func(t *testing.T) {
// 测试传入值 - 修改不应该影响原始数据
p := Person{Name: "Alice", Age: 30}
rfx := New(p)
rfx.Set("Name", "Bob")
// 原始值不应该被修改
if p.Name != "Alice" {
t.Errorf("Expected original value to remain 'Alice', got '%s'", p.Name)
}
// rfx 内部的值应该已经修改
if rfx.Get("Name").String() != "Bob" {
t.Errorf("Expected rfx value to be 'Bob', got '%s'", rfx.Get("Name").String())
}
})
t.Run("New with map pointer", func(t *testing.T) {
m := map[string]any{"name": "Alice"}
rfx := New(&m)
rfx.Set("name", "Bob")
if m["name"] != "Bob" {
t.Errorf("Expected map to be modified, got '%v'", m["name"])
}
})
t.Run("New with map value", func(t *testing.T) {
m := map[string]any{"name": "Alice"}
rfx := New(m)
rfx.Set("name", "Bob")
// 原始 map 不应该被修改
if m["name"] != "Alice" {
t.Errorf("Expected original map to remain 'Alice', got '%v'", m["name"])
}
})
t.Run("New with slice pointer", func(t *testing.T) {
s := []string{"a", "b", "c"}
rfx := New(&s)
rfx.Set("0", "x")
if s[0] != "x" {
t.Errorf("Expected slice to be modified, got '%s'", s[0])
}
})
t.Run("New with slice value", func(t *testing.T) {
s := []string{"a", "b", "c"}
rfx := New(s)
rfx.Set("0", "x")
// 原始切片不应该被修改
if s[0] != "a" {
t.Errorf("Expected original slice to remain 'a', got '%s'", s[0])
}
})
t.Run("New with array value", func(t *testing.T) {
a := [3]string{"a", "b", "c"}
rfx := New(a)
rfx.Set("0", "x")
// 原始数组不应该被修改
if a[0] != "a" {
t.Errorf("Expected original array to remain 'a', got '%s'", a[0])
}
})
t.Run("New with nil pointer", func(t *testing.T) {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Error("New() with non-pointer should panic") t.Error("Expected panic with nil pointer")
} }
}() }()
New(p) // 应该 panic var p *Person
New(p)
})
t.Run("New with unsupported type", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic with unsupported type")
}
}()
New(42) // int 不支持
})
t.Run("New with interface wrapping map", func(t *testing.T) {
// 测试 interface{} 包装的 map
var data any = map[string]string{"name": "Alice"}
rfx := New(data)
if rfx.Get("name").String() != "Alice" {
t.Errorf("Expected 'Alice', got '%s'", rfx.Get("name").String())
}
// 修改不应该影响原始数据(因为是值传递)
rfx.Set("name", "Bob")
originalMap := data.(map[string]string)
if originalMap["name"] != "Alice" {
t.Error("Original map should not be modified")
}
})
t.Run("New with interface wrapping struct", func(t *testing.T) {
// 测试 interface{} 包装的 struct
var data any = Person{Name: "Alice", Age: 30}
rfx := New(data)
if rfx.Get("Name").String() != "Alice" {
t.Errorf("Expected 'Alice', got '%s'", rfx.Get("Name").String())
}
})
t.Run("New with interface wrapping pointer", func(t *testing.T) {
// 测试 interface{} 包装的指针
person := Person{Name: "Alice", Age: 30}
var data any = &person
rfx := New(data)
rfx.Set("Name", "Bob")
// 原始数据应该被修改(因为是指针)
if person.Name != "Bob" {
t.Errorf("Expected original to be modified, got '%s'", person.Name)
}
})
t.Run("New with nil interface", func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic with nil interface")
}
}()
var data any
New(data)
})
} }
// TestNewWithPointer 测试传入指针的情况 // TestNewWithPointer 测试传入指针的情况