first commit

This commit is contained in:
what 2025-12-02 19:52:29 +08:00
commit d9f178020a
8 changed files with 3205 additions and 0 deletions

685
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

141
util.go Normal file
View 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{}
}