核心改进: 1. 新增 normalizeInputValue 函数 - 统一处理输入值的规范化,支持 R 接口、[]R 切片、reflect.Value - 避免重复封装,提升性能和类型安全性 2. 重构 getValueByPath 为独立函数 - 从 rfx 方法提取为独立函数,提高代码复用性 - 更好的职责分离,便于维护和测试 3. 显著增强 setValue 方法的类型转换能力 - 支持切片类型转换:[]any -> []T,自动转换每个元素 - 支持结构体类型转换:map -> struct 或 struct -> struct,按字段名匹配 - 保持指针切片的引用语义,避免不必要的对象复制 4. 新增 tryMapField 函数 - 支持 Map 键名的大小写不敏感访问 - 首字母大写的键会自动尝试小写版本(如 "Host" -> "host") 5. 新增 lowercaseFirst 辅助函数 - 用于首字母小写转换,配合 Map 键名查找 6. 更新测试用例 - 新增指针切片的使用示例 - 展示 []any 包含指针元素的场景 7. 文档全面更新 - 新增"高级类型转换"章节,详细说明切片、结构体、指针切片等转换 - 更新特性列表,突出增强的类型转换能力和 R 接口集成 - 补充 Map 大小写不敏感访问的说明 影响范围: - reflux.go: 使用 normalizeInputValue 统一输入处理 - rfx.go: 重构并增强 setValue、getValueByPath 等核心方法 - util.go: 新增多个辅助函数,代码行数增加 160 行 - rfx_example_test.go: 新增指针切片测试用例 向后兼容:完全兼容现有 API,仅增强内部实现和类型转换能力
967 lines
23 KiB
Markdown
967 lines
23 KiB
Markdown
# Reflux
|
|
|
|
Reflux 是一个 Go 语言包,提供了统一的接口用于访问和操作嵌套的结构体字段、切片元素和映射值。通过字符串路径的方式,可以方便地访问和修改深层嵌套的数据结构。
|
|
|
|
## 特性
|
|
|
|
- 🔍 **统一访问**: 使用字符串路径访问结构体、Map、切片中的任意嵌套值
|
|
- 🎯 **点号路径**: 支持点号分割的路径语法,如 `Get("Address.City")`
|
|
- 🔄 **类型转换**: 基于 [spf13/cast](https://github.com/spf13/cast) 的强大类型转换支持
|
|
- ✏️ **修改数据**: 支持通过路径设置和删除值
|
|
- 🔗 **链式调用**: Set 和 Delete 方法支持链式调用,如 `rfx.Set("name", "Alice").Delete("age").Set("city", "Beijing")`
|
|
- 📋 **深度克隆**: Scope 方法和值传递模式创建深度克隆,修改不影响原始数据
|
|
- 🎭 **灵活模式**: 支持指针和值两种传递方式,提供不同的数据操作语义
|
|
- 🔌 **Interface 支持**: 自动解析 `interface{}` 类型到实际类型
|
|
- 🔤 **大小写不敏感**: 支持使用小写字段名访问和修改结构体字段(包括 Map 键名)
|
|
- 🔑 **键名提取**: Keys 方法可获取结构体或 Map 的所有键名
|
|
- 📝 **JSON 集成**: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化
|
|
- 🎯 **类型安全**: 使用反射但保证类型安全
|
|
- 🔥 **增强类型转换**: 支持切片和结构体之间的智能转换(如 []any -> []T, map -> struct)
|
|
- 🌀 **R 接口集成**: 支持直接传入 R 接口或 []R 切片,无缝集成反射值
|
|
- 🚀 **高性能**: 优化的反射操作,低内存开销
|
|
- 📦 **零依赖**: 仅依赖 Go 标准库和 spf13/cast
|
|
|
|
## 安装
|
|
|
|
```bash
|
|
go get git.fsdpf.net/go/reflux
|
|
```
|
|
|
|
## 快速开始
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"git.fsdpf.net/go/reflux"
|
|
)
|
|
|
|
type User struct {
|
|
Name string
|
|
Age int
|
|
Address struct {
|
|
City string
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
user := User{
|
|
Name: "Alice",
|
|
Age: 30,
|
|
}
|
|
user.Address.City = "Beijing"
|
|
|
|
rfx := reflux.New(&user)
|
|
|
|
// 获取值
|
|
name := rfx.Get("Name").String()
|
|
age := rfx.Get("Age").Int()
|
|
city := rfx.Get("Address", "City").String()
|
|
|
|
fmt.Printf("Name: %s, Age: %d, City: %s\n", name, age, city)
|
|
|
|
// 链式设置值
|
|
rfx.Set("Name", "Bob").Set("Age", 35).Set("Address.City", "Shanghai")
|
|
|
|
fmt.Printf("Updated: %+v\n", user)
|
|
}
|
|
```
|
|
|
|
## 核心功能
|
|
|
|
### 1. 创建 Reflux
|
|
|
|
Reflux 支持**指针**和**值**两种传递方式,提供不同的数据操作语义:
|
|
|
|
#### 指针模式 - 直接修改原始数据
|
|
|
|
```go
|
|
user := User{Name: "Alice"}
|
|
rfx := reflux.New(&user) // 传入指针
|
|
|
|
rfx.Set("Name", "Bob")
|
|
// user.Name 已被修改为 "Bob"
|
|
```
|
|
|
|
#### 值模式 - 深度克隆,不影响原始数据
|
|
|
|
```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)
|
|
|
|
```go
|
|
// 获取顶层字段
|
|
name := rfx.Get("Name").String()
|
|
|
|
// 获取嵌套字段 - 使用多个参数
|
|
city := rfx.Get("Address", "City").String()
|
|
|
|
// 获取嵌套字段 - 使用点号路径
|
|
city := rfx.Get("Address.City").String()
|
|
|
|
// 深层嵌套 - 点号路径
|
|
ceoCity := rfx.Get("Company.CEO.Address.City").String()
|
|
|
|
// 混合使用点号和参数
|
|
value := rfx.Get("User.Profile", "Settings", "Theme").String()
|
|
|
|
// 获取切片元素 - 使用索引
|
|
tag := rfx.Get("Tags", "0").String()
|
|
|
|
// 获取切片元素 - 使用点号
|
|
tag := rfx.Get("Tags.0").String()
|
|
|
|
// 获取 Map 值 - 使用多个参数
|
|
value := rfx.Get("Meta", "key").String()
|
|
|
|
// 获取 Map 值 - 使用点号
|
|
value := rfx.Get("Meta.key").String()
|
|
|
|
// 大小写不敏感访问 - 结构体字段和 Map 键名
|
|
name := rfx.Get("name").String() // 等同于 Get("Name")
|
|
city := rfx.Get("address.city").String() // 等同于 Get("Address.City")
|
|
|
|
// Map 类型也支持大小写转换
|
|
config := map[string]string{"Host": "localhost"}
|
|
rfx := reflux.New(&config)
|
|
host := rfx.Get("host").String() // 自动尝试 "Host"
|
|
```
|
|
|
|
### 3. 作用域 (Scope)
|
|
|
|
`Scope` 方法创建指定路径的**深度克隆**,在克隆上的修改不会影响原始数据:
|
|
|
|
```go
|
|
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 := reflux.New(&person)
|
|
|
|
// 创建 Address 字段的深度克隆
|
|
addressScope := rfx.Scope("Address")
|
|
|
|
// 在克隆上修改值
|
|
addressScope.Set("City", "Shanghai")
|
|
addressScope.Set("Street", "New St")
|
|
|
|
// 原始数据不受影响
|
|
fmt.Println(person.Address.City) // 输出: Beijing
|
|
fmt.Println(person.Address.Street) // 输出: Main St
|
|
|
|
// 克隆数据已修改
|
|
fmt.Println(addressScope.Get("City").String()) // 输出: Shanghai
|
|
fmt.Println(addressScope.Get("Street").String()) // 输出: New St
|
|
|
|
// 不传参数时,克隆整个对象
|
|
clone := rfx.Scope()
|
|
clone.Set("Name", "Bob")
|
|
fmt.Println(person.Name) // 输出: Alice (原始数据不变)
|
|
```
|
|
|
|
**重要**: `Scope` 返回的是深度克隆,与原始数据完全独立。
|
|
|
|
### 4. 设置值 (Set)
|
|
|
|
`Set` 方法使用新的 API 签名: `Set(key string, v any) Reflux`,支持链式调用:
|
|
|
|
```go
|
|
// 链式设置多个值
|
|
rfx.Set("Name", "Bob").Set("Age", 35).Set("Address.City", "Shanghai")
|
|
|
|
// 设置顶层字段
|
|
rfx.Set("Name", "Bob")
|
|
|
|
// 设置嵌套字段 - 使用点号路径
|
|
rfx.Set("Address.City", "Shanghai")
|
|
|
|
// 深层嵌套设置
|
|
rfx.Set("Company.CEO.Address.City", "Guangzhou")
|
|
|
|
// 设置 Map 值
|
|
rfx.Set("Meta.key", "value")
|
|
|
|
// 设置切片元素
|
|
rfx.Set("Tags.0", "newValue")
|
|
|
|
// 大小写不敏感设置
|
|
rfx.Set("name", "Bob") // 等同于 Set("Name", "Bob")
|
|
rfx.Set("address.city", "Shanghai") // 等同于 Set("Address.City", "Shanghai")
|
|
|
|
// 自动类型转换
|
|
rfx.Set("Age", int32(35)) // int32 -> int
|
|
|
|
// 切片类型转换 - 从通用切片转为具体类型
|
|
rfx.Set("Scores", []any{90, 95, 88}) // []any -> []int
|
|
|
|
// 结构体类型转换 - 从 map 填充到 struct
|
|
addressMap := map[string]any{
|
|
"City": "Shanghai",
|
|
"Street": "Nanjing Road",
|
|
}
|
|
rfx.Set("Address", addressMap) // map -> Address struct
|
|
|
|
// 指针切片赋值 - 保持指针引用
|
|
type Item struct {
|
|
Name string
|
|
}
|
|
item1 := &Item{Name: "apple"}
|
|
item2 := &Item{Name: "banana"}
|
|
rfx.Set("Items", []*Item{item1, item2}) // 保持原指针地址
|
|
|
|
// 也支持从 []any 包含指针元素
|
|
rfx.Set("Items", []any{item1, item2}) // 复用指针地址
|
|
|
|
// R 接口和 []R 切片支持
|
|
rfx1 := reflux.New(data1)
|
|
rfx2 := reflux.New(data2)
|
|
rfx.Set("Item", rfx1) // 直接传入 R 接口
|
|
rfx.Set("Items", []R{rfx1, rfx2}) // 传入 R 切片
|
|
|
|
// Map 自动初始化
|
|
rfx.Set("NewMap.key", "value") // 如果 NewMap 是 nil,会自动初始化
|
|
```
|
|
|
|
**注意**: 如果设置失败(路径不存在、类型不匹配等),会 panic。
|
|
|
|
#### 高级类型转换
|
|
|
|
Reflux 提供了强大的智能类型转换能力,支持多种复杂场景:
|
|
|
|
**1. 切片类型转换**
|
|
|
|
支持从通用切片(如 `[]any`)转换为具体类型切片:
|
|
|
|
```go
|
|
type Person struct {
|
|
Scores []int
|
|
Tags []string
|
|
}
|
|
|
|
p := Person{}
|
|
rfx := reflux.New(&p)
|
|
|
|
// []any -> []int
|
|
rfx.Set("Scores", []any{90, 95, 88})
|
|
// p.Scores = []int{90, 95, 88}
|
|
|
|
// []any -> []string (自动类型转换)
|
|
rfx.Set("Tags", []any{"go", "rust", 123})
|
|
// p.Tags = []string{"go", "rust", "123"}
|
|
```
|
|
|
|
**2. 结构体类型转换**
|
|
|
|
支持从 map 或其他结构体填充到目标结构体:
|
|
|
|
```go
|
|
type Address struct {
|
|
City string
|
|
Street string
|
|
}
|
|
|
|
type Person struct {
|
|
Address Address
|
|
}
|
|
|
|
p := Person{}
|
|
rfx := reflux.New(&p)
|
|
|
|
// map -> struct
|
|
addressMap := map[string]any{
|
|
"City": "Shanghai",
|
|
"Street": "Nanjing Road",
|
|
}
|
|
rfx.Set("Address", addressMap)
|
|
// p.Address = Address{City: "Shanghai", Street: "Nanjing Road"}
|
|
|
|
// struct -> struct (按字段名匹配)
|
|
src := Address{City: "Beijing", Street: "Changan"}
|
|
rfx.Set("Address", src)
|
|
```
|
|
|
|
**3. 指针切片处理**
|
|
|
|
对于包含指针的切片,Reflux 会保持指针引用:
|
|
|
|
```go
|
|
type Fruit struct {
|
|
Name string
|
|
}
|
|
|
|
type Basket struct {
|
|
Fruits []*Fruit
|
|
}
|
|
|
|
basket := Basket{}
|
|
rfx := reflux.New(&basket)
|
|
|
|
apple := &Fruit{Name: "apple"}
|
|
banana := &Fruit{Name: "banana"}
|
|
|
|
// 保持指针引用,不创建新对象
|
|
rfx.Set("Fruits", []*Fruit{apple, banana})
|
|
|
|
// 修改原指针对象,basket.Fruits 中会看到相同变化
|
|
apple.Name = "green apple"
|
|
// basket.Fruits[0].Name == "green apple"
|
|
|
|
// 也支持从 []any 赋值,复用指针地址
|
|
rfx.Set("Fruits", []any{apple, banana})
|
|
```
|
|
|
|
**4. R 接口集成**
|
|
|
|
支持直接传入 R 接口或 []R 切片:
|
|
|
|
```go
|
|
// 传入单个 R 接口
|
|
data1 := map[string]string{"key": "value"}
|
|
rfx1 := reflux.New(data1)
|
|
rfx.Set("Item", rfx1) // 自动提取底层值
|
|
|
|
// 传入 []R 切片
|
|
data2 := map[string]int{"count": 10}
|
|
rfx2 := reflux.New(data2)
|
|
rfx.Set("Items", []R{rfx1, rfx2}) // 自动转换为底层切片
|
|
```
|
|
|
|
**5. reflect.Value 支持**
|
|
|
|
可以直接传入 `reflect.Value`,避免重复封装:
|
|
|
|
```go
|
|
import "reflect"
|
|
|
|
val := reflect.ValueOf(someData)
|
|
rfx.Set("Field", val) // 直接使用,不重复包装
|
|
```
|
|
|
|
这些高级转换特性使得 Reflux 在处理复杂数据结构和动态类型场景时更加灵活和强大。
|
|
|
|
#### 切片追加 (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 自身:
|
|
|
|
```go
|
|
// 删除 Map 键 - 使用多个参数
|
|
rfx.Delete("Meta", "key")
|
|
|
|
// 删除 Map 键 - 使用点号
|
|
rfx.Delete("Meta.key")
|
|
|
|
// 删除切片元素 - 使用索引
|
|
rfx.Delete("Tags", "1") // 删除索引 1 的元素
|
|
|
|
// 删除切片元素 - 使用点号
|
|
rfx.Delete("Tags.1")
|
|
|
|
// 链式调用 - 删除多个值
|
|
rfx.Delete("Meta.key1").Delete("Meta.key2").Set("Meta.key3", "value")
|
|
```
|
|
|
|
**注意**: 如果删除失败(路径不存在、类型不支持删除等),会 panic。
|
|
|
|
### 6. 检查存在性 (Exists)
|
|
|
|
```go
|
|
if rfx.Exists("Name") {
|
|
fmt.Println("Name field exists")
|
|
}
|
|
|
|
// 使用多个参数检查嵌套字段
|
|
if rfx.Exists("Address", "City") {
|
|
fmt.Println("Nested field exists")
|
|
}
|
|
|
|
// 使用点号路径检查嵌套字段
|
|
if rfx.Exists("Address.City") {
|
|
fmt.Println("Nested field exists")
|
|
}
|
|
|
|
// 检查深层嵌套
|
|
if rfx.Exists("Company.CEO.Address.City") {
|
|
fmt.Println("Deep nested field exists")
|
|
}
|
|
```
|
|
|
|
### 7. 获取键名 (Keys)
|
|
|
|
```go
|
|
type User struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
user := User{Name: "Alice", Age: 30}
|
|
rfx := reflux.New(&user)
|
|
|
|
// 获取结构体的所有字段名
|
|
keys := rfx.Keys()
|
|
fmt.Println(keys) // 输出: [Name Age]
|
|
|
|
// 对于 Map
|
|
config := map[string]any{
|
|
"host": "localhost",
|
|
"port": 8080,
|
|
}
|
|
sm2 := reflux.New(&config)
|
|
keys2 := sm2.Keys()
|
|
fmt.Println(keys2) // 输出: [host port]
|
|
|
|
// 获取嵌套对象的键名
|
|
addressKeys := rfx.Get("Address").Keys()
|
|
```
|
|
|
|
### 8. 数组操作 (Array)
|
|
|
|
```go
|
|
// 将切片转换为 Reflux 数组
|
|
tags := rfx.Get("Tags").Array()
|
|
for i, tag := range tags {
|
|
fmt.Printf("Tag[%d]: %s\n", i, tag.String())
|
|
}
|
|
|
|
// 可以对数组元素进行进一步操作
|
|
users := rfx.Get("Users").Array()
|
|
for i, user := range users {
|
|
name := user.Get("Name").String()
|
|
age := user.Get("Age").Int()
|
|
fmt.Printf("User[%d]: %s, %d\n", i, name, age)
|
|
}
|
|
```
|
|
|
|
### 9. 类型转换
|
|
|
|
#### 基本类型
|
|
|
|
```go
|
|
age := rfx.Get("Age").Int() // int
|
|
age64 := rfx.Get("Age").Int64() // int64
|
|
age32 := rfx.Get("Age").Int32() // int32
|
|
age16 := rfx.Get("Age").Int16() // int16
|
|
age8 := rfx.Get("Age").Int8() // int8
|
|
uage := rfx.Get("Age").Uint() // uint
|
|
uage64 := rfx.Get("Age").Uint64() // uint64
|
|
uage32 := rfx.Get("Age").Uint32() // uint32
|
|
uage16 := rfx.Get("Age").Uint16() // uint16
|
|
uage8 := rfx.Get("Age").Uint8() // uint8
|
|
fage := rfx.Get("Age").Float64() // float64
|
|
fage32 := rfx.Get("Age").Float32() // float32
|
|
sage := rfx.Get("Age").String() // string
|
|
active := rfx.Get("Active").Bool() // bool
|
|
```
|
|
|
|
**注意**: 类型转换使用 [spf13/cast](https://github.com/spf13/cast) 库,支持智能类型转换。转换失败会 panic。
|
|
|
|
#### Map 类型
|
|
|
|
```go
|
|
// map[string]string
|
|
strMap := rfx.Get("Meta").StringMapString()
|
|
|
|
// map[string]int
|
|
intMap := rfx.Get("Scores").StringMapInt()
|
|
|
|
// map[string]int64
|
|
int64Map := rfx.Get("Scores").StringMapInt64()
|
|
|
|
// map[string]bool
|
|
boolMap := rfx.Get("Flags").StringMapBool()
|
|
|
|
// map[string]any
|
|
anyMap := rfx.Get("Data").StringMap()
|
|
|
|
// map[string][]string
|
|
sliceMap := rfx.Get("Tags").StringMapStringSlice()
|
|
```
|
|
|
|
#### 切片类型
|
|
|
|
```go
|
|
// []string
|
|
tags := rfx.Get("Tags").StringSlice()
|
|
|
|
// []int
|
|
scores := rfx.Get("Scores").IntSlice()
|
|
|
|
// []bool
|
|
flags := rfx.Get("Flags").BoolSlice()
|
|
|
|
// []any
|
|
items := rfx.Get("Items").Slice()
|
|
```
|
|
|
|
### 10. 底层访问
|
|
|
|
```go
|
|
// 获取 reflect.Value
|
|
val := rfx.Get("Name").Value()
|
|
|
|
// 获取指针
|
|
ptr := rfx.Get("Name").Ptr()
|
|
|
|
// 获取 any 类型
|
|
any := rfx.Get("Name").Any()
|
|
```
|
|
|
|
#### 与 json.Unmarshal() 配合使用
|
|
|
|
`Ptr()` 方法返回的指针可以直接用于 `json.Unmarshal()`,实现动态的 JSON 反序列化:
|
|
|
|
```go
|
|
import (
|
|
"encoding/json"
|
|
"git.fsdpf.net/go/reflux"
|
|
)
|
|
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
Address Address
|
|
Meta map[string]string
|
|
Tags []string
|
|
}
|
|
|
|
type Address struct {
|
|
City string
|
|
Street string
|
|
ZipCode int
|
|
}
|
|
|
|
func main() {
|
|
p := Person{
|
|
Name: "Alice",
|
|
Address: Address{City: "OldCity"},
|
|
Meta: make(map[string]string),
|
|
}
|
|
|
|
rfx := reflux.New(&p)
|
|
|
|
// 1. 更新嵌套结构体
|
|
addressJSON := []byte(`{
|
|
"City": "Shanghai",
|
|
"Street": "Nanjing Road",
|
|
"ZipCode": 200000
|
|
}`)
|
|
json.Unmarshal(addressJSON, rfx.Get("Address").Ptr())
|
|
// p.Address 已被完整更新
|
|
|
|
// 2. 更新 Map 字段
|
|
metaJSON := []byte(`{
|
|
"key1": "value1",
|
|
"key2": "value2"
|
|
}`)
|
|
json.Unmarshal(metaJSON, rfx.Get("Meta").Ptr())
|
|
// p.Meta 已被填充
|
|
|
|
// 3. 更新切片字段
|
|
tagsJSON := []byte(`["go", "rust", "python"]`)
|
|
json.Unmarshal(tagsJSON, rfx.Get("Tags").Ptr())
|
|
// p.Tags 已被替换
|
|
|
|
// 4. 更新整个对象
|
|
personJSON := []byte(`{
|
|
"Name": "Bob",
|
|
"Age": 35
|
|
}`)
|
|
json.Unmarshal(personJSON, rfx.Ptr())
|
|
// 整个 Person 对象被更新
|
|
}
|
|
```
|
|
|
|
**使用场景**:
|
|
- 动态配置更新: 从 JSON 文件或 API 响应更新配置的特定部分
|
|
- 部分数据刷新: 只更新对象的某个嵌套字段,其他字段保持不变
|
|
- 插件系统: 动态加载和更新插件配置
|
|
- 热更新: 在运行时更新应用配置而无需重启
|
|
|
|
## 使用场景
|
|
|
|
### 1. 配置文件处理
|
|
|
|
```go
|
|
type Config struct {
|
|
Database struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
Redis struct {
|
|
Addr string
|
|
}
|
|
}
|
|
|
|
config := loadConfig()
|
|
rfx := reflux.New(&config)
|
|
|
|
// 动态读取配置
|
|
dbHost := rfx.Get("Database", "Host").String()
|
|
dbPort := rfx.Get("Database", "Port").Int()
|
|
|
|
// 链式修改配置
|
|
rfx.Set("Database.Host", "localhost").Set("Database.Port", 3306)
|
|
```
|
|
|
|
### 2. API 响应处理
|
|
|
|
```go
|
|
response := map[string]any{
|
|
"user": map[string]any{
|
|
"name": "Alice",
|
|
"age": 30,
|
|
},
|
|
"status": "success",
|
|
}
|
|
|
|
rfx := reflux.New(&response)
|
|
userName := rfx.Get("user", "name").String()
|
|
userAge := rfx.Get("user", "age").Int()
|
|
|
|
// 修改响应数据
|
|
rfx.Set("user.name", "Bob").Set("status", "updated")
|
|
```
|
|
|
|
### 3. 动态表单数据
|
|
|
|
```go
|
|
formData := map[string]any{
|
|
"name": "Bob",
|
|
"email": "bob@example.com",
|
|
"age": "25", // 字符串形式的数字
|
|
}
|
|
|
|
rfx := reflux.New(&formData)
|
|
|
|
// 自动类型转换
|
|
name := rfx.Get("name").String()
|
|
age := rfx.Get("age").Int() // "25" -> 25
|
|
|
|
// 验证和修改
|
|
if age < 18 {
|
|
rfx.Set("verified", false)
|
|
}
|
|
```
|
|
|
|
### 4. 测试数据构建
|
|
|
|
```go
|
|
testUser := User{}
|
|
rfx := reflux.New(&testUser)
|
|
|
|
// 链式快速设置测试数据
|
|
rfx.Set("Name", "TestUser").
|
|
Set("Age", 25).
|
|
Set("Address.City", "Beijing").
|
|
Set("Address.Street", "Test St")
|
|
```
|
|
|
|
### 5. 数据克隆和隔离
|
|
|
|
```go
|
|
original := Config{Host: "localhost"}
|
|
rfx := reflux.New(&original)
|
|
|
|
// 创建深度克隆进行测试
|
|
testConfig := rfx.Scope()
|
|
testConfig.Set("Host", "test-server").Set("Port", 9999)
|
|
|
|
// 原始配置不受影响
|
|
fmt.Println(original.Host) // 输出: localhost
|
|
```
|
|
|
|
### 6. JSON 动态更新
|
|
|
|
使用 `Ptr()` 方法配合 `json.Unmarshal()` 实现动态更新:
|
|
|
|
```go
|
|
type AppConfig struct {
|
|
Server ServerConfig
|
|
Database DatabaseConfig
|
|
Features map[string]bool
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Driver string
|
|
DSN string
|
|
}
|
|
|
|
func main() {
|
|
config := AppConfig{
|
|
Server: ServerConfig{
|
|
Host: "localhost",
|
|
Port: 8080,
|
|
},
|
|
Features: make(map[string]bool),
|
|
}
|
|
|
|
rfx := reflux.New(&config)
|
|
|
|
// 从配置文件或 API 只更新 Server 配置
|
|
serverJSON := []byte(`{
|
|
"Host": "production.example.com",
|
|
"Port": 443
|
|
}`)
|
|
json.Unmarshal(serverJSON, rfx.Get("Server").Ptr())
|
|
|
|
// 动态启用功能开关
|
|
featuresJSON := []byte(`{
|
|
"newFeature": true,
|
|
"experimentalUI": false
|
|
}`)
|
|
json.Unmarshal(featuresJSON, rfx.Get("Features").Ptr())
|
|
|
|
// config.Server 已更新,config.Database 保持不变
|
|
fmt.Printf("%+v\n", config)
|
|
}
|
|
```
|
|
|
|
这种方式特别适合:
|
|
- **微服务配置**: 从配置中心动态更新特定模块配置
|
|
- **A/B 测试**: 实时更新功能开关
|
|
- **插件热加载**: 更新插件配置而无需重启
|
|
- **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 支持灵活的点号路径语法:
|
|
|
|
```go
|
|
// 以下调用是等价的:
|
|
rfx.Get("a.b.c")
|
|
rfx.Get("a.b", "c")
|
|
rfx.Get("a", "b.c")
|
|
rfx.Get("a", "b", "c")
|
|
|
|
// 空段会被忽略
|
|
rfx.Get("a..b") // 等价于 rfx.Get("a", "b")
|
|
rfx.Get(".a.b.") // 等价于 rfx.Get("a", "b")
|
|
|
|
// 点号路径适用于所有方法
|
|
rfx.Set("a.b.c", value)
|
|
rfx.Delete("a.b.c")
|
|
rfx.Exists("a.b.c")
|
|
rfx.Scope("a.b.c")
|
|
|
|
// 访问切片元素
|
|
tag := rfx.Get("Tags.0").String()
|
|
|
|
// 访问 Map 值
|
|
value := rfx.Get("Config.Database.Host").String()
|
|
```
|
|
|
|
## API 文档
|
|
|
|
### Reflux 接口
|
|
|
|
```go
|
|
type Reflux interface {
|
|
// 路径操作
|
|
Get(p ...string) Reflux
|
|
Scope(p ...string) Reflux
|
|
|
|
// 修改操作 (支持链式调用)
|
|
Set(key string, v any) Reflux
|
|
Delete(p ...string) Reflux
|
|
Exists(p ...string) bool
|
|
|
|
// 数组和键操作
|
|
Array() []Reflux
|
|
Keys() []string
|
|
|
|
// 底层访问
|
|
Value() reflect.Value
|
|
Ptr() any
|
|
Any() any
|
|
|
|
// 基本类型转换
|
|
Bool() bool
|
|
Int() int
|
|
Int8() int8
|
|
Int16() int16
|
|
Int32() int32
|
|
Int64() int64
|
|
Uint() uint
|
|
Uint8() uint8
|
|
Uint16() uint16
|
|
Uint32() uint32
|
|
Uint64() uint64
|
|
Float32() float32
|
|
Float64() float64
|
|
String() string
|
|
|
|
// Map 类型转换
|
|
StringMapString() map[string]string
|
|
StringMapStringSlice() map[string][]string
|
|
StringMapBool() map[string]bool
|
|
StringMapInt() map[string]int
|
|
StringMapInt64() map[string]int64
|
|
StringMap() map[string]any
|
|
|
|
// Slice 类型转换
|
|
Slice() []any
|
|
BoolSlice() []bool
|
|
StringSlice() []string
|
|
IntSlice() []int
|
|
}
|
|
```
|
|
|
|
## 注意事项
|
|
|
|
1. **指针 vs 值传递**:
|
|
- 传入**指针** (`New(&data)`) 时,修改会影响原始数据
|
|
- 传入**值** (`New(data)`) 时,会创建深度克隆,修改不影响原始数据
|
|
- 对于 map 和 slice 等引用类型,值模式也会完全克隆
|
|
2. **Interface 支持**: 支持 `interface{}` 类型,会自动解析到实际类型并保持正确的指针/值语义
|
|
3. **Scope 行为**: `Scope` 返回深度克隆,修改不会影响原始数据
|
|
4. **链式调用**: Set 和 Delete 方法都支持链式调用,返回 Reflux 自身
|
|
5. **类型转换**: 使用 spf13/cast 进行类型转换,转换失败会 panic
|
|
6. **切片删除**: 删除切片元素会创建新切片并重新赋值
|
|
7. **Map 初始化**: 对 nil map 调用 Set 会自动初始化 map
|
|
8. **大小写不敏感**: 支持使用小写字段名访问结构体字段
|
|
9. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal()
|
|
10. **并发安全**: Reflux 本身不是并发安全的,需要外部同步
|
|
11. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确
|