- 如果目标字段是指针类型,它会尝试直接赋值(如果类型兼容),或创建一个新指针并递归设置其指向的值。 - `rfx_example_test.go` 中新增了测试用例,以验证对 `*string` 等指针字段的设置功能。 |
||
|---|---|---|
| go.mod | ||
| go.sum | ||
| README.md | ||
| reflux.go | ||
| rfx_example_test.go | ||
| rfx_test.go | ||
| rfx.go | ||
| util.go | ||
Reflux
Reflux 是一个 Go 语言包,提供了统一的接口用于访问和操作嵌套的结构体字段、切片元素和映射值。通过字符串路径的方式,可以方便地访问和修改深层嵌套的数据结构。
特性
- 🔍 统一访问: 使用字符串路径访问结构体、Map、切片中的任意嵌套值
- 🎯 点号路径: 支持点号分割的路径语法,如
Get("Address.City") - 🔄 类型转换: 基于 spf13/cast 的强大类型转换支持
- ✏️ 修改数据: 支持通过路径设置和删除值
- 🔗 链式调用: Set 和 Delete 方法支持链式调用,如
rfx.Set("name", "Alice").Delete("age").Set("city", "Beijing") - 📋 深度克隆: Scope 方法和值传递模式创建深度克隆,修改不影响原始数据
- 🎭 灵活模式: 支持指针和值两种传递方式,提供不同的数据操作语义
- 🔌 Interface 支持: 自动解析
interface{}类型到实际类型 - 🔤 大小写不敏感: 支持使用小写字段名访问和修改结构体字段
- 🔑 键名提取: Keys 方法可获取结构体或 Map 的所有键名
- 📝 JSON 集成: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化
- 🎯 类型安全: 使用反射但保证类型安全
- 🚀 高性能: 优化的反射操作,低内存开销
- 📦 零依赖: 仅依赖 Go 标准库和 spf13/cast
安装
go get git.fsdpf.net/go/reflux
快速开始
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 支持指针和值两种传递方式,提供不同的数据操作语义:
指针模式 - 直接修改原始数据
user := User{Name: "Alice"}
rfx := reflux.New(&user) // 传入指针
rfx.Set("Name", "Bob")
// user.Name 已被修改为 "Bob"
值模式 - 深度克隆,不影响原始数据
user := User{Name: "Alice"}
rfx := reflux.New(user) // 传入值
rfx.Set("Name", "Bob")
// user.Name 仍然是 "Alice"
// rfx 内部是独立的深度克隆
Interface 类型支持
Reflux 支持 interface{} (或 any) 类型,会自动解析到实际类型:
// 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)
// 获取顶层字段
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()
// 大小写不敏感访问 (自动转换为正确的字段名)
name := rfx.Get("name").String() // 等同于 Get("Name")
city := rfx.Get("address.city").String() // 等同于 Get("Address.City")
3. 作用域 (Scope)
Scope 方法创建指定路径的深度克隆,在克隆上的修改不会影响原始数据:
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,支持链式调用:
// 链式设置多个值
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
// Map 自动初始化
rfx.Set("NewMap.key", "value") // 如果 NewMap 是 nil,会自动初始化
注意: 如果设置失败(路径不存在、类型不匹配等),会 panic。
5. 删除值 (Delete)
Delete 方法支持链式调用,返回 Reflux 自身:
// 删除 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)
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)
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)
// 将切片转换为 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. 类型转换
基本类型
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 库,支持智能类型转换。转换失败会 panic。
Map 类型
// 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()
切片类型
// []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. 底层访问
// 获取 reflect.Value
val := rfx.Get("Name").Value()
// 获取指针
ptr := rfx.Get("Name").Ptr()
// 获取 any 类型
any := rfx.Get("Name").Any()
与 json.Unmarshal() 配合使用
Ptr() 方法返回的指针可以直接用于 json.Unmarshal(),实现动态的 JSON 反序列化:
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. 配置文件处理
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 响应处理
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. 动态表单数据
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. 测试数据构建
testUser := User{}
rfx := reflux.New(&testUser)
// 链式快速设置测试数据
rfx.Set("Name", "TestUser").
Set("Age", 25).
Set("Address.City", "Beijing").
Set("Address.Street", "Test St")
5. 数据克隆和隔离
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() 实现动态更新:
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{} 类型处理未知类型的数据:
// 函数返回 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 支持灵活的点号路径语法:
// 以下调用是等价的:
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 接口
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
}
注意事项
- 指针 vs 值传递:
- 传入指针 (
New(&data)) 时,修改会影响原始数据 - 传入值 (
New(data)) 时,会创建深度克隆,修改不影响原始数据 - 对于 map 和 slice 等引用类型,值模式也会完全克隆
- 传入指针 (
- Interface 支持: 支持
interface{}类型,会自动解析到实际类型并保持正确的指针/值语义 - Scope 行为:
Scope返回深度克隆,修改不会影响原始数据 - 链式调用: Set 和 Delete 方法都支持链式调用,返回 Reflux 自身
- 类型转换: 使用 spf13/cast 进行类型转换,转换失败会 panic
- 切片删除: 删除切片元素会创建新切片并重新赋值
- Map 初始化: 对 nil map 调用 Set 会自动初始化 map
- 大小写不敏感: 支持使用小写字段名访问结构体字段
- JSON 集成: Ptr() 返回的指针可以安全地用于 json.Unmarshal()
- 并发安全: Reflux 本身不是并发安全的,需要外部同步
- 错误处理: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确