first commit
This commit is contained in:
commit
d9f178020a
685
README.md
Normal file
685
README.md
Normal file
@ -0,0 +1,685 @@
|
||||
# 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 方法创建深度克隆,修改不影响原始数据
|
||||
- 🔤 **大小写不敏感**: 支持使用小写字段名访问和修改结构体字段
|
||||
- 🔑 **键名提取**: Keys 方法可获取结构体或 Map 的所有键名
|
||||
- 📝 **JSON 集成**: Ptr() 方法返回的指针可直接用于 json.Unmarshal(),实现动态 JSON 反序列化
|
||||
- 🎯 **类型安全**: 使用反射但保证类型安全
|
||||
- 🚀 **高性能**: 优化的反射操作,低内存开销
|
||||
- 📦 **零依赖**: 仅依赖 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
|
||||
|
||||
```go
|
||||
user := User{Name: "Alice"}
|
||||
rfx := reflux.New(&user) // 传入指针以支持修改
|
||||
```
|
||||
|
||||
**重要**: `New()` 函数必须传入指针类型,否则会 panic。
|
||||
|
||||
### 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()
|
||||
|
||||
// 大小写不敏感访问 (自动转换为正确的字段名)
|
||||
name := rfx.Get("name").String() // 等同于 Get("Name")
|
||||
city := rfx.Get("address.city").String() // 等同于 Get("Address.City")
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
// Map 自动初始化
|
||||
rfx.Set("NewMap.key", "value") // 如果 NewMap 是 nil,会自动初始化
|
||||
```
|
||||
|
||||
**注意**: 如果设置失败(路径不存在、类型不匹配等),会 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 返回的部分字段
|
||||
|
||||
## 路径语法
|
||||
|
||||
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. **指针传递**: `New()` 函数必须传递指针,否则会 panic
|
||||
2. **Scope 行为**: `Scope` 返回深度克隆,修改不会影响原始数据
|
||||
3. **链式调用**: Set 和 Delete 方法都支持链式调用,返回 Reflux 自身
|
||||
4. **类型转换**: 使用 spf13/cast 进行类型转换,转换失败会 panic
|
||||
5. **切片删除**: 删除切片元素会创建新切片并重新赋值
|
||||
6. **Map 初始化**: 对 nil map 调用 Set 会自动初始化 map
|
||||
7. **大小写不敏感**: 支持使用小写字段名访问结构体字段
|
||||
8. **JSON 集成**: Ptr() 返回的指针可以安全地用于 json.Unmarshal()
|
||||
9. **并发安全**: Reflux 本身不是并发安全的,需要外部同步
|
||||
10. **错误处理**: 大多数转换、设置和删除操作失败时会 panic,请确保路径和类型正确
|
||||
|
||||
## 示例代码
|
||||
|
||||
查看测试文件获取更多示例:
|
||||
- [structmap_test.go](structmap_test.go) - 核心功能测试
|
||||
- [structmap_example_test.go](structmap_example_test.go) - 可执行示例
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git.fsdpf.net/go/reflux
|
||||
|
||||
go 1.23.10
|
||||
|
||||
require github.com/spf13/cast v1.10.0 // indirect
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
131
reflux.go
Normal file
131
reflux.go
Normal file
@ -0,0 +1,131 @@
|
||||
package reflux
|
||||
|
||||
import "reflect"
|
||||
|
||||
// R 提供了一个统一的接口,用于访问和操作嵌套的结构体字段、切片元素和映射值
|
||||
type R interface {
|
||||
// Get 通过路径获取嵌套字段的值,返回一个新的 R 实例
|
||||
// 参数 p 为路径片段,例如 Get("user", "profile", "name")
|
||||
Get(p ...string) R
|
||||
|
||||
// Scope 类似 Get,但用于创建一个指定路径的作用域视图
|
||||
// 后续操作将基于这个作用域进行
|
||||
Scope(p ...string) R
|
||||
|
||||
// Set 设置指定路径的值,支持链式调用
|
||||
// 参数 key 为路径(支持点号分割),v 为要设置的值
|
||||
// 返回当前 R 实例以支持链式调用
|
||||
// 示例: rfx.Set("name", "Alice").Set("age", 30)
|
||||
Set(key string, v any) R
|
||||
|
||||
// Delete 删除指定路径的值
|
||||
// 参数 p 为路径片段
|
||||
// 返回当前 R 实例以支持链式调用
|
||||
Delete(p ...string) R
|
||||
|
||||
// Exists 检查指定路径的值是否存在
|
||||
// 参数 p 为路径片段
|
||||
// 返回 true 表示存在,false 表示不存在
|
||||
Exists(p ...string) bool
|
||||
|
||||
// Array 将当前值转换为 R 切片
|
||||
// 适用于数组或切片类型的值
|
||||
Array() []R
|
||||
|
||||
// Keys 返回当前映射或结构体的所有键名
|
||||
Keys() []string
|
||||
|
||||
// Value 返回底层的 reflect.Value
|
||||
// 用于需要直接操作反射值的场景
|
||||
Value() reflect.Value
|
||||
|
||||
// Ptr 返回指向当前值的指针
|
||||
Ptr() any
|
||||
|
||||
// Any 将当前值转换为 any 类型
|
||||
Any() any
|
||||
|
||||
// Bool 将当前值转换为 bool 类型
|
||||
Bool() bool
|
||||
|
||||
// Float64 将当前值转换为 float64 类型
|
||||
Float64() float64
|
||||
|
||||
// Float32 将当前值转换为 float32 类型
|
||||
Float32() float32
|
||||
|
||||
// Int64 将当前值转换为 int64 类型
|
||||
Int64() int64
|
||||
|
||||
// Int32 将当前值转换为 int32 类型
|
||||
Int32() int32
|
||||
|
||||
// Int16 将当前值转换为 int16 类型
|
||||
Int16() int16
|
||||
|
||||
// Int8 将当前值转换为 int8 类型
|
||||
Int8() int8
|
||||
|
||||
// Int 将当前值转换为 int 类型
|
||||
Int() int
|
||||
|
||||
// Uint 将当前值转换为 uint 类型
|
||||
Uint() uint
|
||||
|
||||
// Uint64 将当前值转换为 uint64 类型
|
||||
Uint64() uint64
|
||||
|
||||
// Uint32 将当前值转换为 uint32 类型
|
||||
Uint32() uint32
|
||||
|
||||
// Uint16 将当前值转换为 uint16 类型
|
||||
Uint16() uint16
|
||||
|
||||
// Uint8 将当前值转换为 uint8 类型
|
||||
Uint8() uint8
|
||||
|
||||
// String 将当前值转换为 string 类型
|
||||
String() string
|
||||
|
||||
// StringMapString 将当前值转换为 map[string]string 类型
|
||||
StringMapString() map[string]string
|
||||
|
||||
// StringMapStringSlice 将当前值转换为 map[string][]string 类型
|
||||
StringMapStringSlice() map[string][]string
|
||||
|
||||
// StringMapBool 将当前值转换为 map[string]bool 类型
|
||||
StringMapBool() map[string]bool
|
||||
|
||||
// StringMapInt 将当前值转换为 map[string]int 类型
|
||||
StringMapInt() map[string]int
|
||||
|
||||
// StringMapInt64 将当前值转换为 map[string]int64 类型
|
||||
StringMapInt64() map[string]int64
|
||||
|
||||
// StringMap 将当前值转换为 map[string]any 类型
|
||||
StringMap() map[string]any
|
||||
|
||||
// Slice 将当前值转换为 []any 切片
|
||||
Slice() []any
|
||||
|
||||
// BoolSlice 将当前值转换为 []bool 切片
|
||||
BoolSlice() []bool
|
||||
|
||||
// StringSlice 将当前值转换为 []string 切片
|
||||
StringSlice() []string
|
||||
|
||||
// IntSlice 将当前值转换为 []int 切片
|
||||
IntSlice() []int
|
||||
}
|
||||
|
||||
// New 创建一个新的 R 实例
|
||||
// 参数 v 必须是指针类型,否则会 panic
|
||||
// 返回一个 R 接口实例,可用于访问和操作嵌套的字段、元素和键值对
|
||||
func New(v any) R {
|
||||
rv := reflect.ValueOf(v)
|
||||
// 如果传入的不是指针,panic
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
panic("structmap: New() requires a pointer argument")
|
||||
}
|
||||
return &rfx{value: rv}
|
||||
}
|
||||
689
rfx.go
Normal file
689
rfx.go
Normal file
@ -0,0 +1,689 @@
|
||||
package reflux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// rfx 是 R 接口的具体实现
|
||||
// 通过封装 reflect.Value 提供了对结构体、切片、映射等类型的统一访问方式
|
||||
type rfx struct {
|
||||
// value 存储底层的反射值
|
||||
// 该值应该是指针类型,以支持修改操作
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// Get 通过路径获取嵌套字段的值
|
||||
func (r *rfx) Get(p ...string) R {
|
||||
return &rfx{value: r.getValueByPath(p...)}
|
||||
}
|
||||
|
||||
// Scope 创建一个指定路径的作用域视图(深度克隆)
|
||||
func (r *rfx) Scope(p ...string) R {
|
||||
v := r.getValueByPath(p...)
|
||||
if !v.IsValid() {
|
||||
return &rfx{value: reflect.Value{}}
|
||||
}
|
||||
|
||||
// 深度克隆值
|
||||
cloned := DeepClone(v)
|
||||
return &rfx{value: cloned}
|
||||
}
|
||||
|
||||
// getValueByPath 通过路径获取值的辅助方法
|
||||
// 支持两种路径格式:
|
||||
// 1. 多个参数: Get("Address", "City")
|
||||
// 2. 点号分割: Get("Address.City") 或混合使用 Get("Address.City", "ZipCode")
|
||||
func (r *rfx) getValueByPath(p ...string) reflect.Value {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// 展开所有路径片段,支持点号分割
|
||||
keys := expandPath(p...)
|
||||
|
||||
for _, key := range keys {
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
v = tryStructField(v, key)
|
||||
case reflect.Map:
|
||||
v = v.MapIndex(reflect.ValueOf(key))
|
||||
case reflect.Slice, reflect.Array:
|
||||
// 尝试将 key 转换为索引
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err == nil && idx >= 0 && idx < v.Len() {
|
||||
v = v.Index(idx)
|
||||
} else {
|
||||
return reflect.Value{}
|
||||
}
|
||||
default:
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
// 解引用指针和接口
|
||||
for v.IsValid() && (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Set 设置指定路径的值,支持链式调用
|
||||
// 如果路径不存在或设置失败,会 panic
|
||||
func (r *rfx) Set(key string, v any) R {
|
||||
// 展开路径
|
||||
keys := expandPath(key)
|
||||
if len(keys) == 0 {
|
||||
panic("rfx: empty path")
|
||||
}
|
||||
|
||||
// 如果只有一个键,直接设置
|
||||
if len(keys) == 1 {
|
||||
target := r.getParentValue()
|
||||
if !target.IsValid() {
|
||||
panic(fmt.Sprintf("rfx: invalid value for path '%s'", key))
|
||||
}
|
||||
if !target.CanSet() {
|
||||
panic(fmt.Sprintf("rfx: cannot set value at path '%s'", key))
|
||||
}
|
||||
if !r.setFieldValue(target, keys[0], v) {
|
||||
panic(fmt.Sprintf("rfx: failed to set value at path '%s'", key))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// 多个键的情况,需要特殊处理 map 中的 struct
|
||||
if !r.setNestedValue(r.value, keys, v) {
|
||||
panic(fmt.Sprintf("rfx: failed to set value at path '%s'", key))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// setNestedValue 递归设置嵌套值,特殊处理 map 中的 struct
|
||||
func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) bool {
|
||||
// 解引用指针和接口
|
||||
for current.Kind() == reflect.Ptr || current.Kind() == reflect.Interface {
|
||||
if current.IsNil() {
|
||||
return false
|
||||
}
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
if !current.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果只剩一个键,直接设置
|
||||
if len(keys) == 1 {
|
||||
if !current.CanSet() {
|
||||
return false
|
||||
}
|
||||
return r.setFieldValue(current, keys[0], v)
|
||||
}
|
||||
|
||||
// 多个键的情况
|
||||
firstKey := keys[0]
|
||||
remainingKeys := keys[1:]
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Struct:
|
||||
field := tryStructField(current, firstKey)
|
||||
if !field.IsValid() {
|
||||
return false
|
||||
}
|
||||
return r.setNestedValue(field, remainingKeys, v)
|
||||
|
||||
case reflect.Map:
|
||||
// Map 的特殊处理
|
||||
mapKey := reflect.ValueOf(firstKey)
|
||||
mapValue := current.MapIndex(mapKey)
|
||||
|
||||
if !mapValue.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
// 创建 map 值的副本以便修改
|
||||
valueCopy := reflect.New(mapValue.Type()).Elem()
|
||||
valueCopy.Set(mapValue)
|
||||
|
||||
// 在副本上递归设置值
|
||||
if !r.setNestedValue(valueCopy, remainingKeys, v) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 将修改后的值设置回 map
|
||||
current.SetMapIndex(mapKey, valueCopy)
|
||||
return true
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
idx, err := strconv.Atoi(firstKey)
|
||||
if err != nil || idx < 0 || idx >= current.Len() {
|
||||
return false
|
||||
}
|
||||
elem := current.Index(idx)
|
||||
if !elem.IsValid() {
|
||||
return false
|
||||
}
|
||||
return r.setNestedValue(elem, remainingKeys, v)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getParentValue 获取父级值的辅助方法
|
||||
func (r *rfx) getParentValue(p ...string) reflect.Value {
|
||||
if len(p) == 0 {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
return r.getValueByPath(p...)
|
||||
}
|
||||
|
||||
// setFieldValue 设置字段值的辅助方法
|
||||
func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool {
|
||||
for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface {
|
||||
if target.IsNil() {
|
||||
return false
|
||||
}
|
||||
target = target.Elem()
|
||||
}
|
||||
|
||||
switch target.Kind() {
|
||||
case reflect.Struct:
|
||||
field := tryStructField(target, key)
|
||||
if !field.IsValid() || !field.CanSet() {
|
||||
return false
|
||||
}
|
||||
return r.setValue(field, v)
|
||||
case reflect.Map:
|
||||
if target.IsNil() {
|
||||
target.Set(reflect.MakeMap(target.Type()))
|
||||
}
|
||||
val := reflect.ValueOf(v)
|
||||
if !val.Type().AssignableTo(target.Type().Elem()) {
|
||||
// 尝试转换
|
||||
if val.Type().ConvertibleTo(target.Type().Elem()) {
|
||||
val = val.Convert(target.Type().Elem())
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
target.SetMapIndex(reflect.ValueOf(key), val)
|
||||
return true
|
||||
case reflect.Slice, reflect.Array:
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err != nil || idx < 0 || idx >= target.Len() {
|
||||
return false
|
||||
}
|
||||
elem := target.Index(idx)
|
||||
if !elem.CanSet() {
|
||||
return false
|
||||
}
|
||||
return r.setValue(elem, v)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setValue 设置值的辅助方法
|
||||
// 使用 cast 库进行智能类型转换,支持更多的转换场景
|
||||
func (r *rfx) setValue(field reflect.Value, v any) bool {
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
// 尝试直接赋值(类型完全匹配)
|
||||
if val.Type().AssignableTo(field.Type()) {
|
||||
field.Set(val)
|
||||
return true
|
||||
}
|
||||
|
||||
// 优先使用 cast 进行智能类型转换
|
||||
// 这样可以处理 string <-> number, number <-> bool 等常见转换
|
||||
targetType := field.Type()
|
||||
var converted any
|
||||
var err error
|
||||
|
||||
switch targetType.Kind() {
|
||||
case reflect.Bool:
|
||||
converted, err = cast.ToBoolE(v)
|
||||
case reflect.Int:
|
||||
converted, err = cast.ToIntE(v)
|
||||
case reflect.Int8:
|
||||
converted, err = cast.ToInt8E(v)
|
||||
case reflect.Int16:
|
||||
converted, err = cast.ToInt16E(v)
|
||||
case reflect.Int32:
|
||||
converted, err = cast.ToInt32E(v)
|
||||
case reflect.Int64:
|
||||
converted, err = cast.ToInt64E(v)
|
||||
case reflect.Uint:
|
||||
converted, err = cast.ToUintE(v)
|
||||
case reflect.Uint8:
|
||||
converted, err = cast.ToUint8E(v)
|
||||
case reflect.Uint16:
|
||||
converted, err = cast.ToUint16E(v)
|
||||
case reflect.Uint32:
|
||||
converted, err = cast.ToUint32E(v)
|
||||
case reflect.Uint64:
|
||||
converted, err = cast.ToUint64E(v)
|
||||
case reflect.Float32:
|
||||
converted, err = cast.ToFloat32E(v)
|
||||
case reflect.Float64:
|
||||
converted, err = cast.ToFloat64E(v)
|
||||
case reflect.String:
|
||||
converted, err = cast.ToStringE(v)
|
||||
default:
|
||||
// 对于 cast 不支持的类型,尝试标准的反射类型转换
|
||||
if val.Type().ConvertibleTo(field.Type()) {
|
||||
field.Set(val.Convert(field.Type()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// 如果 cast 失败,尝试标准的反射类型转换作为后备
|
||||
if val.Type().ConvertibleTo(field.Type()) {
|
||||
field.Set(val.Convert(field.Type()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(converted))
|
||||
return true
|
||||
}
|
||||
|
||||
// Delete 删除指定路径的值,支持链式调用
|
||||
// 如果删除失败会 panic
|
||||
func (r *rfx) Delete(p ...string) R {
|
||||
if len(p) == 0 {
|
||||
panic("rfx: empty path")
|
||||
}
|
||||
|
||||
// 展开路径
|
||||
keys := expandPath(p...)
|
||||
if len(keys) == 0 {
|
||||
panic("rfx: empty path")
|
||||
}
|
||||
|
||||
target := r.getParentValue(keys[:len(keys)-1]...)
|
||||
if !target.IsValid() {
|
||||
panic(fmt.Sprintf("rfx: invalid path '%s'", strings.Join(keys[:len(keys)-1], ".")))
|
||||
}
|
||||
|
||||
lastKey := keys[len(keys)-1]
|
||||
for target.Kind() == reflect.Ptr || target.Kind() == reflect.Interface {
|
||||
if target.IsNil() {
|
||||
panic(fmt.Sprintf("rfx: nil value at path '%s'", strings.Join(keys[:len(keys)-1], ".")))
|
||||
}
|
||||
target = target.Elem()
|
||||
}
|
||||
|
||||
switch target.Kind() {
|
||||
case reflect.Map:
|
||||
target.SetMapIndex(reflect.ValueOf(lastKey), reflect.Value{})
|
||||
return r
|
||||
case reflect.Slice:
|
||||
idx, err := strconv.Atoi(lastKey)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: invalid slice index '%s'", lastKey))
|
||||
}
|
||||
if idx < 0 || idx >= target.Len() {
|
||||
panic(fmt.Sprintf("rfx: slice index out of range: %d", idx))
|
||||
}
|
||||
// 删除切片元素
|
||||
newSlice := reflect.AppendSlice(
|
||||
target.Slice(0, idx),
|
||||
target.Slice(idx+1, target.Len()),
|
||||
)
|
||||
target.Set(newSlice)
|
||||
return r
|
||||
default:
|
||||
panic(fmt.Sprintf("rfx: cannot delete from type %s", target.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// Exists 检查指定路径的值是否存在
|
||||
func (r *rfx) Exists(p ...string) bool {
|
||||
v := r.getValueByPath(p...)
|
||||
return v.IsValid()
|
||||
}
|
||||
|
||||
// Array 将当前值转换为 R 切片
|
||||
func (r *rfx) Array() []R {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]R, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
result[i] = &rfx{value: v.Index(i)}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// keys 返回当前映射或结构体的所有键名
|
||||
func (r *rfx) Keys() []string {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
keys := v.MapKeys()
|
||||
result := make([]string, len(keys))
|
||||
for i, k := range keys {
|
||||
result[i] = fmt.Sprint(k.Interface())
|
||||
}
|
||||
return result
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
result := make([]string, t.NumField())
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
result[i] = t.Field(i).Name
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value 返回底层的 reflect.Value
|
||||
func (r *rfx) Value() reflect.Value {
|
||||
return r.value
|
||||
}
|
||||
|
||||
// Ptr 返回指向当前值的指针
|
||||
func (r *rfx) Ptr() any {
|
||||
v := r.value
|
||||
if v.Kind() == reflect.Ptr {
|
||||
return v.Interface()
|
||||
}
|
||||
if v.CanAddr() {
|
||||
return v.Addr().Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Any 将当前值转换为 any 类型
|
||||
func (r *rfx) Any() any {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return v.Interface()
|
||||
}
|
||||
|
||||
// Bool 将当前值转换为 bool 类型
|
||||
func (r *rfx) Bool() bool {
|
||||
result, err := cast.ToBoolE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to bool: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Float64 将当前值转换为 float64 类型
|
||||
func (r *rfx) Float64() float64 {
|
||||
result, err := cast.ToFloat64E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to float64: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Float32 将当前值转换为 float32 类型
|
||||
func (r *rfx) Float32() float32 {
|
||||
result, err := cast.ToFloat32E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to float32: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Int64 将当前值转换为 int64 类型
|
||||
func (r *rfx) Int64() int64 {
|
||||
result, err := cast.ToInt64E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to int64: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Int32 将当前值转换为 int32 类型
|
||||
func (r *rfx) Int32() int32 {
|
||||
result, err := cast.ToInt32E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to int32: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Int16 将当前值转换为 int16 类型
|
||||
func (r *rfx) Int16() int16 {
|
||||
result, err := cast.ToInt16E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to int16: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Int8 将当前值转换为 int8 类型
|
||||
func (r *rfx) Int8() int8 {
|
||||
result, err := cast.ToInt8E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to int8: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Int 将当前值转换为 int 类型
|
||||
func (r *rfx) Int() int {
|
||||
result, err := cast.ToIntE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to int: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Uint 将当前值转换为 uint 类型
|
||||
func (r *rfx) Uint() uint {
|
||||
result, err := cast.ToUintE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to uint: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Uint64 将当前值转换为 uint64 类型
|
||||
func (r *rfx) Uint64() uint64 {
|
||||
result, err := cast.ToUint64E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to uint64: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Uint32 将当前值转换为 uint32 类型
|
||||
func (r *rfx) Uint32() uint32 {
|
||||
result, err := cast.ToUint32E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to uint32: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Uint16 将当前值转换为 uint16 类型
|
||||
func (r *rfx) Uint16() uint16 {
|
||||
result, err := cast.ToUint16E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to uint16: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Uint8 将当前值转换为 uint8 类型
|
||||
func (r *rfx) Uint8() uint8 {
|
||||
result, err := cast.ToUint8E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to uint8: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// String 将当前值转换为 string 类型
|
||||
func (r *rfx) String() string {
|
||||
result, err := cast.ToStringE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to string: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMapString 将当前值转换为 map[string]string 类型
|
||||
func (r *rfx) StringMapString() map[string]string {
|
||||
result, err := cast.ToStringMapStringE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string]string: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMapStringSlice 将当前值转换为 map[string][]string 类型
|
||||
func (r *rfx) StringMapStringSlice() map[string][]string {
|
||||
result, err := cast.ToStringMapStringSliceE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string][]string: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMapBool 将当前值转换为 map[string]bool 类型
|
||||
func (r *rfx) StringMapBool() map[string]bool {
|
||||
result, err := cast.ToStringMapBoolE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string]bool: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMapInt 将当前值转换为 map[string]int 类型
|
||||
func (r *rfx) StringMapInt() map[string]int {
|
||||
result, err := cast.ToStringMapIntE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string]int: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMapInt64 将当前值转换为 map[string]int64 类型
|
||||
func (r *rfx) StringMapInt64() map[string]int64 {
|
||||
result, err := cast.ToStringMapInt64E(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string]int64: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringMap 将当前值转换为 map[string]any 类型
|
||||
func (r *rfx) StringMap() map[string]any {
|
||||
result, err := cast.ToStringMapE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to map[string]any: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Slice 将当前值转换为 []any 切片
|
||||
func (r *rfx) Slice() []any {
|
||||
v := r.value
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
if !v.IsValid() || (v.Kind() != reflect.Slice && v.Kind() != reflect.Array) {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]any, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
elem := v.Index(i)
|
||||
for elem.Kind() == reflect.Ptr || elem.Kind() == reflect.Interface {
|
||||
if elem.IsNil() {
|
||||
break
|
||||
}
|
||||
elem = elem.Elem()
|
||||
}
|
||||
if elem.IsValid() {
|
||||
result[i] = elem.Interface()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BoolSlice 将当前值转换为 []bool 切片
|
||||
func (r *rfx) BoolSlice() []bool {
|
||||
result, err := cast.ToBoolSliceE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to []bool: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StringSlice 将当前值转换为 []string 切片
|
||||
func (r *rfx) StringSlice() []string {
|
||||
result, err := cast.ToStringSliceE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to []string: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IntSlice 将当前值转换为 []int 切片
|
||||
func (r *rfx) IntSlice() []int {
|
||||
result, err := cast.ToIntSliceE(r.Any())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("rfx: failed to convert to []int: %v", err))
|
||||
}
|
||||
return result
|
||||
}
|
||||
268
rfx_example_test.go
Normal file
268
rfx_example_test.go
Normal file
@ -0,0 +1,268 @@
|
||||
package reflux
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ExampleNew_withMap 演示如何使用 New 函数传入 map
|
||||
func ExampleNew_withMap() {
|
||||
// 创建一个 map
|
||||
config := map[string]any{
|
||||
"host": "localhost",
|
||||
"port": 8080,
|
||||
"ssl": true,
|
||||
}
|
||||
|
||||
// 使用 New 创建 StructMap (必须传入指针)
|
||||
rfx := New(&config)
|
||||
|
||||
// 获取值
|
||||
host := rfx.Get("host").String()
|
||||
port := rfx.Get("port").Int()
|
||||
ssl := rfx.Get("ssl").Bool()
|
||||
|
||||
fmt.Printf("Host: %s\n", host)
|
||||
fmt.Printf("Port: %d\n", port)
|
||||
fmt.Printf("SSL: %t\n", ssl)
|
||||
|
||||
// 修改值
|
||||
rfx.Set("host", "127.0.0.1")
|
||||
rfx.Set("port", 9090)
|
||||
|
||||
// 添加新键
|
||||
rfx.Set("timeout", 30)
|
||||
|
||||
fmt.Printf("Updated host: %s\n", config["host"])
|
||||
fmt.Printf("Updated port: %v\n", config["port"])
|
||||
fmt.Printf("New timeout: %v\n", config["timeout"])
|
||||
|
||||
// Output:
|
||||
// Host: localhost
|
||||
// Port: 8080
|
||||
// SSL: true
|
||||
// Updated host: 127.0.0.1
|
||||
// Updated port: 9090
|
||||
// New timeout: 30
|
||||
}
|
||||
|
||||
// ExampleStructMap_Scope_noArgs 演示如何使用 Scope() 不传参数来复制整个对象
|
||||
func ExampleStructMap_Scope_noArgs() {
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
Host: "localhost",
|
||||
Port: 8080,
|
||||
}
|
||||
|
||||
rfx := New(&cfg)
|
||||
|
||||
// Scope 不传参数,创建整个对象的深度克隆
|
||||
clone := rfx.Scope()
|
||||
|
||||
// 在克隆上修改值
|
||||
clone.Set("Host", "127.0.0.1")
|
||||
clone.Set("Port", 9090)
|
||||
|
||||
// 原对象不受影响
|
||||
fmt.Printf("Original - Host: %s, Port: %d\n", cfg.Host, cfg.Port)
|
||||
|
||||
// 克隆对象已修改
|
||||
fmt.Printf("Clone - Host: %s, Port: %d\n",
|
||||
clone.Get("Host").String(),
|
||||
clone.Get("Port").Int())
|
||||
|
||||
// Output:
|
||||
// Original - Host: localhost, Port: 8080
|
||||
// Clone - Host: 127.0.0.1, Port: 9090
|
||||
}
|
||||
|
||||
// ExampleStructMap_Scope_withPath 演示如何使用 Scope() 传入路径来复制嵌套对象
|
||||
func ExampleStructMap_Scope_withPath() {
|
||||
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)
|
||||
|
||||
// 创建 Address 字段的 Scope (深度克隆)
|
||||
addressScope := rfx.Scope("Address")
|
||||
|
||||
// 在 Scope 上修改值
|
||||
addressScope.Set("City", "Shanghai")
|
||||
addressScope.Set("Street", "New St")
|
||||
|
||||
// 原对象不受影响
|
||||
fmt.Printf("Original Address - City: %s, Street: %s\n",
|
||||
person.Address.City, person.Address.Street)
|
||||
|
||||
// Scope 对象已修改
|
||||
fmt.Printf("Scope Address - City: %s, Street: %s\n",
|
||||
addressScope.Get("City").String(),
|
||||
addressScope.Get("Street").String())
|
||||
|
||||
// Output:
|
||||
// Original Address - City: Beijing, Street: Main St
|
||||
// Scope Address - City: Shanghai, Street: New St
|
||||
}
|
||||
|
||||
// ExampleNew_withNestedMap 演示如何使用嵌套的 map
|
||||
func ExampleNew_withNestedMap() {
|
||||
data := map[string]any{
|
||||
"user": map[string]any{
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
},
|
||||
"tags": []string{"developer", "gopher"},
|
||||
}
|
||||
|
||||
rfx := New(&data)
|
||||
|
||||
// 获取嵌套的值
|
||||
userName := rfx.Get("user", "name").String()
|
||||
userAge := rfx.Get("user", "age").Int()
|
||||
firstTag := rfx.Get("tags", "0").String()
|
||||
|
||||
fmt.Printf("User: %s, Age: %d\n", userName, userAge)
|
||||
fmt.Printf("First tag: %s\n", firstTag)
|
||||
|
||||
// 使用点号语法访问嵌套值
|
||||
userName2 := rfx.Get("user.name").String()
|
||||
fmt.Printf("User (dot notation): %s\n", userName2)
|
||||
|
||||
// Output:
|
||||
// User: Alice, Age: 30
|
||||
// First tag: developer
|
||||
// User (dot notation): Alice
|
||||
}
|
||||
|
||||
// ExampleStructMap_lowercaseFields 演示如何使用小写字段名访问 struct
|
||||
func ExampleStructMap_lowercaseFields() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
person := Person{
|
||||
Name: "Alice",
|
||||
Age: 30,
|
||||
}
|
||||
|
||||
rfx := New(&person)
|
||||
|
||||
// 使用小写字段名获取值(自动转换为大写)
|
||||
name := rfx.Get("name").String()
|
||||
age := rfx.Get("age").Int()
|
||||
|
||||
fmt.Printf("Name: %s, Age: %d\n", name, age)
|
||||
|
||||
// 使用小写字段名设置值
|
||||
rfx.Set("name", "Bob")
|
||||
rfx.Set("age", 35)
|
||||
|
||||
fmt.Printf("Updated - Name: %s, Age: %d\n", person.Name, person.Age)
|
||||
|
||||
// Output:
|
||||
// Name: Alice, Age: 30
|
||||
// Updated - Name: Bob, Age: 35
|
||||
}
|
||||
|
||||
// ExampleStructMap_lowercaseNestedFields 演示如何使用小写字段名访问嵌套 struct
|
||||
func ExampleStructMap_lowercaseNestedFields() {
|
||||
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)
|
||||
|
||||
// 使用小写字段名访问嵌套字段
|
||||
city := rfx.Get("address", "city").String()
|
||||
street := rfx.Get("address.street").String()
|
||||
|
||||
fmt.Printf("City: %s, Street: %s\n", city, street)
|
||||
|
||||
// 使用小写字段名设置嵌套字段
|
||||
rfx.Set("address.city", "Shanghai")
|
||||
rfx.Set("address.street", "New Street")
|
||||
|
||||
fmt.Printf("Updated City: %s, Street: %s\n",
|
||||
person.Address.City, person.Address.Street)
|
||||
|
||||
// Output:
|
||||
// City: Beijing, Street: Main St
|
||||
// Updated City: Shanghai, Street: New Street
|
||||
}
|
||||
|
||||
// ExampleStructMap_castConversion 演示使用 cast 进行智能类型转换
|
||||
func ExampleStructMap_castConversion() {
|
||||
type Config struct {
|
||||
Port int
|
||||
Host string
|
||||
Enabled bool
|
||||
Timeout float64
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
rfx := New(&config)
|
||||
|
||||
// 字符串转数字
|
||||
rfx.Set("Port", "8080")
|
||||
fmt.Printf("Port (string->int): %d\n", config.Port)
|
||||
|
||||
// 数字转字符串
|
||||
rfx.Set("Host", 12345)
|
||||
fmt.Printf("Host (int->string): %s\n", config.Host)
|
||||
|
||||
// 字符串转布尔
|
||||
rfx.Set("Enabled", "true")
|
||||
fmt.Printf("Enabled (string->bool): %t\n", config.Enabled)
|
||||
|
||||
// 字符串转浮点数
|
||||
rfx.Set("Timeout", "30.5")
|
||||
fmt.Printf("Timeout (string->float): %.1f\n", config.Timeout)
|
||||
|
||||
// 链式调用不同类型转换
|
||||
rfx.Set("Port", "9090").
|
||||
Set("Host", "localhost").
|
||||
Set("Enabled", "1").
|
||||
Set("Timeout", "60")
|
||||
|
||||
fmt.Printf("\nAfter chain: Port=%d, Host=%s, Enabled=%t, Timeout=%.0f\n",
|
||||
config.Port, config.Host, config.Enabled, config.Timeout)
|
||||
|
||||
// Output:
|
||||
// Port (string->int): 8080
|
||||
// Host (int->string): 12345
|
||||
// Enabled (string->bool): true
|
||||
// Timeout (string->float): 30.5
|
||||
//
|
||||
// After chain: Port=9090, Host=localhost, Enabled=true, Timeout=60
|
||||
}
|
||||
1284
rfx_test.go
Normal file
1284
rfx_test.go
Normal file
File diff suppressed because it is too large
Load Diff
141
util.go
Normal file
141
util.go
Normal file
@ -0,0 +1,141 @@
|
||||
package reflux
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// DeepClone 深度克隆一个 reflect.Value
|
||||
func DeepClone(v reflect.Value) reflect.Value {
|
||||
// 解引用指针和接口以获取实际值
|
||||
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
// 创建新值的指针以便修改
|
||||
cloned := reflect.New(v.Type())
|
||||
CloneValue(cloned.Elem(), v)
|
||||
return cloned
|
||||
}
|
||||
|
||||
// CloneValue 递归克隆值
|
||||
func CloneValue(dst, src reflect.Value) {
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch src.Kind() {
|
||||
case reflect.Ptr:
|
||||
if src.IsNil() {
|
||||
return
|
||||
}
|
||||
dst.Set(reflect.New(src.Type().Elem()))
|
||||
CloneValue(dst.Elem(), src.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
if src.IsNil() {
|
||||
return
|
||||
}
|
||||
cloned := DeepClone(src.Elem())
|
||||
if cloned.IsValid() {
|
||||
dst.Set(cloned)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
for i := 0; i < src.NumField(); i++ {
|
||||
if dst.Field(i).CanSet() {
|
||||
CloneValue(dst.Field(i), src.Field(i))
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if src.IsNil() {
|
||||
return
|
||||
}
|
||||
dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
CloneValue(dst.Index(i), src.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Array:
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
CloneValue(dst.Index(i), src.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if src.IsNil() {
|
||||
return
|
||||
}
|
||||
dst.Set(reflect.MakeMap(src.Type()))
|
||||
for _, key := range src.MapKeys() {
|
||||
val := src.MapIndex(key)
|
||||
// Map 的值需要特殊处理
|
||||
newVal := reflect.New(val.Type()).Elem()
|
||||
CloneValue(newVal, val)
|
||||
dst.SetMapIndex(key, newVal)
|
||||
}
|
||||
|
||||
default:
|
||||
// 对于基本类型(int, string, bool, float等),直接赋值
|
||||
if dst.CanSet() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expandPath 展开路径,支持点号分割
|
||||
// 例如: expandPath("a.b", "c") -> []string{"a", "b", "c"}
|
||||
func expandPath(p ...string) []string {
|
||||
var result []string
|
||||
for _, segment := range p {
|
||||
// 按点号分割
|
||||
parts := strings.Split(segment, ".")
|
||||
for _, part := range parts {
|
||||
if part != "" { // 忽略空字符串
|
||||
result = append(result, part)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// capitalizeFirst 将字符串首字母转换为大写
|
||||
// 用于处理 struct 字段名,使其符合 Go 的公开字段命名规范
|
||||
func capitalizeFirst(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
runes := []rune(s)
|
||||
runes[0] = unicode.ToUpper(runes[0])
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// tryStructField 尝试获取 struct 字段,支持小写字段名自动转大写
|
||||
// 1. 首先尝试原始字段名
|
||||
// 2. 如果失败且首字母是小写,尝试首字母大写的版本
|
||||
func tryStructField(v reflect.Value, fieldName string) reflect.Value {
|
||||
// 首先尝试原始字段名
|
||||
field := v.FieldByName(fieldName)
|
||||
if field.IsValid() {
|
||||
return field
|
||||
}
|
||||
|
||||
// 如果字段名首字母是小写,尝试首字母大写
|
||||
if len(fieldName) > 0 && unicode.IsLower(rune(fieldName[0])) {
|
||||
capitalizedName := capitalizeFirst(fieldName)
|
||||
field = v.FieldByName(capitalizedName)
|
||||
if field.IsValid() {
|
||||
return field
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.Value{}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user