feat: 增强 map 大小写不敏感支持和函数重命名
- 新增 tryMapFieldKey 函数,返回 map 中实际存在的键(支持大小写转换) - 优化 setFieldValue 方法,使用 tryMapFieldKey 查找已存在字段并更新 - 优化 setNestedValue 方法,修复嵌套 map 大小写处理和 interface 包装问题 - 重命名函数以提高代码清晰度: - tryStructField → tryStructFieldValue - tryMapField → tryMapFieldValue - tryMapFieldWithKey → tryMapFieldKey - 新增 rfx_map_case_test.go 包含 16 个测试用例,覆盖基本、嵌套、边界等场景
This commit is contained in:
parent
fbba6f9a30
commit
72d670be0b
39
rfx.go
39
rfx.go
@ -131,7 +131,7 @@ func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) bool {
|
|||||||
|
|
||||||
switch current.Kind() {
|
switch current.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
field := tryStructField(current, firstKey)
|
field := tryStructFieldValue(current, firstKey)
|
||||||
if !field.IsValid() {
|
if !field.IsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -139,24 +139,34 @@ func (r *rfx) setNestedValue(current reflect.Value, keys []string, v any) bool {
|
|||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
// Map 的特殊处理
|
// Map 的特殊处理
|
||||||
mapKey := reflect.ValueOf(firstKey)
|
// 使用 tryMapFieldKey 获取实际的键
|
||||||
mapValue := current.MapIndex(mapKey)
|
actualKey := tryMapFieldKey(current, firstKey)
|
||||||
|
if !actualKey.IsValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
mapValue := current.MapIndex(actualKey)
|
||||||
if !mapValue.IsValid() {
|
if !mapValue.IsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解开 interface 包装获取实际的值
|
||||||
|
actualValue := mapValue
|
||||||
|
for actualValue.Kind() == reflect.Interface && !actualValue.IsNil() {
|
||||||
|
actualValue = actualValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
// 创建 map 值的副本以便修改
|
// 创建 map 值的副本以便修改
|
||||||
valueCopy := reflect.New(mapValue.Type()).Elem()
|
valueCopy := reflect.New(actualValue.Type()).Elem()
|
||||||
valueCopy.Set(mapValue)
|
valueCopy.Set(actualValue)
|
||||||
|
|
||||||
// 在副本上递归设置值
|
// 在副本上递归设置值
|
||||||
if !r.setNestedValue(valueCopy, remainingKeys, v) {
|
if !r.setNestedValue(valueCopy, remainingKeys, v) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将修改后的值设置回 map
|
// 将修改后的值设置回 map,使用实际找到的键
|
||||||
current.SetMapIndex(mapKey, valueCopy)
|
current.SetMapIndex(actualKey, valueCopy)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
@ -200,7 +210,7 @@ func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool {
|
|||||||
|
|
||||||
switch target.Kind() {
|
switch target.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
field := tryStructField(target, key)
|
field := tryStructFieldValue(target, key)
|
||||||
if !field.IsValid() || !field.CanSet() {
|
if !field.IsValid() || !field.CanSet() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -210,6 +220,19 @@ func (r *rfx) setFieldValue(target reflect.Value, key string, v any) bool {
|
|||||||
target.Set(reflect.MakeMap(target.Type()))
|
target.Set(reflect.MakeMap(target.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 先尝试使用 tryMapFieldKey 检查字段是否已存在并获取实际的键
|
||||||
|
actualKey := tryMapFieldKey(target, key)
|
||||||
|
if actualKey.IsValid() {
|
||||||
|
// 字段已存在,创建新值用于设置
|
||||||
|
newValue := reflect.New(target.Type().Elem()).Elem()
|
||||||
|
if !r.setValue(newValue, v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
target.SetMapIndex(actualKey, newValue)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字段不存在,创建新的 map 值
|
||||||
// 处理 nil 值的情况
|
// 处理 nil 值的情况
|
||||||
if v == nil {
|
if v == nil {
|
||||||
target.SetMapIndex(reflect.ValueOf(key), reflect.Zero(target.Type().Elem()))
|
target.SetMapIndex(reflect.ValueOf(key), reflect.Zero(target.Type().Elem()))
|
||||||
|
|||||||
435
rfx_map_case_test.go
Normal file
435
rfx_map_case_test.go
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
package reflux
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TestMapCaseInsensitiveKeys 测试 map 大小写不敏感的键访问
|
||||||
|
func TestMapCaseInsensitiveKeys(t *testing.T) {
|
||||||
|
t.Run("Get with lowercase key when map has uppercase", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 30,
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键访问小写键
|
||||||
|
name := rfx.Get("Name").String()
|
||||||
|
if name != "Alice" {
|
||||||
|
t.Errorf("Expected 'Alice', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := rfx.Get("Age").Int()
|
||||||
|
if age != 30 {
|
||||||
|
t.Errorf("Expected 30, got %d", age)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set with uppercase key to map with lowercase keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键设置值
|
||||||
|
rfx.Set("Name", "Bob")
|
||||||
|
|
||||||
|
// 验证小写键被更新
|
||||||
|
if m["name"] != "Bob" {
|
||||||
|
t.Errorf("Expected 'Bob', got '%v'", m["name"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set with lowercase key creates new entry", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"Name": "Alice",
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用小写键设置值(应该创建新条目)
|
||||||
|
rfx.Set("age", 25)
|
||||||
|
|
||||||
|
// 验证新条目被创建
|
||||||
|
if m["age"] != 25 {
|
||||||
|
t.Errorf("Expected 25, got '%v'", m["age"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证原始大写键仍然存在
|
||||||
|
if m["Name"] != "Alice" {
|
||||||
|
t.Errorf("Expected 'Alice', got '%v'", m["Name"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Exists with case-insensitive key", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键检查是否存在
|
||||||
|
if !rfx.Exists("Name") {
|
||||||
|
t.Error("Expected key 'Name' to exist (case-insensitive)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用小写键检查
|
||||||
|
if !rfx.Exists("name") {
|
||||||
|
t.Error("Expected key 'name' to exist")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMapNestedCaseInsensitiveKeys 测试嵌套 map 的大小写不敏感访问
|
||||||
|
func TestMapNestedCaseInsensitiveKeys(t *testing.T) {
|
||||||
|
t.Run("Get nested map with mixed case keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键访问嵌套的小写键
|
||||||
|
name := rfx.Get("User", "Name").String()
|
||||||
|
if name != "Alice" {
|
||||||
|
t.Errorf("Expected 'Alice', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := rfx.Get("User", "Age").Int()
|
||||||
|
if age != 30 {
|
||||||
|
t.Errorf("Expected 30, got %d", age)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set nested map with mixed case keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键设置嵌套值
|
||||||
|
rfx.Set("User.Name", "Bob")
|
||||||
|
|
||||||
|
// 验证值被更新
|
||||||
|
userMap := m["user"].(map[string]any)
|
||||||
|
if userMap["name"] != "Bob" {
|
||||||
|
t.Errorf("Expected 'Bob', got '%v'", userMap["name"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set deeply nested map with case-insensitive keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"data": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"profile": map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键访问深层嵌套
|
||||||
|
rfx.Set("Data.User.Profile.Name", "Charlie")
|
||||||
|
|
||||||
|
// 验证值被更新
|
||||||
|
dataMap := m["data"].(map[string]any)
|
||||||
|
userMap := dataMap["user"].(map[string]any)
|
||||||
|
profileMap := userMap["profile"].(map[string]any)
|
||||||
|
if profileMap["name"] != "Charlie" {
|
||||||
|
t.Errorf("Expected 'Charlie', got '%v'", profileMap["name"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Get from nested map with all uppercase path", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"company": map[string]any{
|
||||||
|
"department": map[string]any{
|
||||||
|
"name": "Engineering",
|
||||||
|
"employee": map[string]any{"count": 50},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用全大写路径访问
|
||||||
|
name := rfx.Get("Company", "Department", "Name").String()
|
||||||
|
if name != "Engineering" {
|
||||||
|
t.Errorf("Expected 'Engineering', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := rfx.Get("Company", "Department", "Employee", "Count").Int()
|
||||||
|
if count != 50 {
|
||||||
|
t.Errorf("Expected 50, got %d", count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set creates nested structure with mixed case", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"settings": map[string]any{},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 设置嵌套值,使用大写键访问
|
||||||
|
rfx.Set("Settings.theme", "dark")
|
||||||
|
rfx.Set("Settings.language", "en")
|
||||||
|
|
||||||
|
settingsMap := m["settings"].(map[string]any)
|
||||||
|
if settingsMap["theme"] != "dark" {
|
||||||
|
t.Errorf("Expected 'dark', got '%v'", settingsMap["theme"])
|
||||||
|
}
|
||||||
|
if settingsMap["language"] != "en" {
|
||||||
|
t.Errorf("Expected 'en', got '%v'", settingsMap["language"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMapCaseInsensitiveWithStructs 测试 map 和 struct 混合时的大小写处理
|
||||||
|
func TestMapCaseInsensitiveWithStructs(t *testing.T) {
|
||||||
|
type Address struct {
|
||||||
|
City string
|
||||||
|
Street string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Struct embedded in map with case-insensitive access", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"user": User{
|
||||||
|
Name: "Alice",
|
||||||
|
Age: 30,
|
||||||
|
Address: Address{
|
||||||
|
City: "Beijing",
|
||||||
|
Street: "Main St",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写 User 访问小写 user 键
|
||||||
|
name := rfx.Get("User", "Name").String()
|
||||||
|
if name != "Alice" {
|
||||||
|
t.Errorf("Expected 'Alice', got '%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
city := rfx.Get("User", "Address", "City").String()
|
||||||
|
if city != "Beijing" {
|
||||||
|
t.Errorf("Expected 'Beijing', got '%s'", city)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set struct field through map with case-insensitive key (pointer)", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"user": &User{
|
||||||
|
Name: "Alice",
|
||||||
|
Age: 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键路径设置 struct 字段(通过指针)
|
||||||
|
rfx.Set("User.Name", "Bob")
|
||||||
|
|
||||||
|
user := m["user"].(*User)
|
||||||
|
if user.Name != "Bob" {
|
||||||
|
t.Errorf("Expected 'Bob', got '%s'", user.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Map inside struct with case-insensitive access", func(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Settings map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{
|
||||||
|
Settings: map[string]any{
|
||||||
|
"theme": "light",
|
||||||
|
"lang": "en",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&config)
|
||||||
|
|
||||||
|
// 使用大写键访问 map 中的小写键
|
||||||
|
theme := rfx.Get("Settings", "Theme").String()
|
||||||
|
if theme != "light" {
|
||||||
|
t.Errorf("Expected 'light', got '%s'", theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置值
|
||||||
|
rfx.Set("Settings.Lang", "zh")
|
||||||
|
if config.Settings["lang"] != "zh" {
|
||||||
|
t.Errorf("Expected 'zh', got '%v'", config.Settings["lang"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMapCaseInsensitiveEdgeCases 测试大小写处理的边界情况
|
||||||
|
func TestMapCaseInsensitiveEdgeCases(t *testing.T) {
|
||||||
|
t.Run("Single character keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2,
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写访问小写
|
||||||
|
valA := rfx.Get("A").Int()
|
||||||
|
if valA != 1 {
|
||||||
|
t.Errorf("Expected 1, got %d", valA)
|
||||||
|
}
|
||||||
|
|
||||||
|
valB := rfx.Get("B").Int()
|
||||||
|
if valB != 2 {
|
||||||
|
t.Errorf("Expected 2, got %d", valB)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Unicode characters in keys", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"名字": "Alice",
|
||||||
|
"年龄": 30,
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
name := rfx.Get("名字").String()
|
||||||
|
if name != "Alice" {
|
||||||
|
t.Errorf("Expected 'Alice', got '%s'", name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Multiple nested maps with same key names", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"level1": map[string]any{
|
||||||
|
"name": "L1",
|
||||||
|
"level2": map[string]any{
|
||||||
|
"name": "L2",
|
||||||
|
"level3": map[string]any{
|
||||||
|
"name": "L3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写路径访问
|
||||||
|
l1Name := rfx.Get("Level1", "Name").String()
|
||||||
|
if l1Name != "L1" {
|
||||||
|
t.Errorf("Expected 'L1', got '%s'", l1Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
l2Name := rfx.Get("Level1", "Level2", "Name").String()
|
||||||
|
if l2Name != "L2" {
|
||||||
|
t.Errorf("Expected 'L2', got '%s'", l2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
l3Name := rfx.Get("Level1.Level2.Level3.Name").String()
|
||||||
|
if l3Name != "L3" {
|
||||||
|
t.Errorf("Expected 'L3', got '%s'", l3Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set value updates existing lowercase key", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"config": map[string]any{
|
||||||
|
"timeout": 30,
|
||||||
|
"retry": 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键设置已存在的小写键
|
||||||
|
rfx.Set("Config.Timeout", 60)
|
||||||
|
rfx.Set("Config.Retry", 5)
|
||||||
|
|
||||||
|
configMap := m["config"].(map[string]any)
|
||||||
|
if configMap["timeout"] != 60 {
|
||||||
|
t.Errorf("Expected 60, got %v", configMap["timeout"])
|
||||||
|
}
|
||||||
|
if configMap["retry"] != 5 {
|
||||||
|
t.Errorf("Expected 5, got %v", configMap["retry"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Mixed case keys with numbers", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"server1": map[string]any{"host": "localhost"},
|
||||||
|
"server2": map[string]any{"host": "example.com"},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
host1 := rfx.Get("Server1", "Host").String()
|
||||||
|
if host1 != "localhost" {
|
||||||
|
t.Errorf("Expected 'localhost', got '%s'", host1)
|
||||||
|
}
|
||||||
|
|
||||||
|
host2 := rfx.Get("Server2.Host").String()
|
||||||
|
if host2 != "example.com" {
|
||||||
|
t.Errorf("Expected 'example.com', got '%s'", host2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMapCaseInsensitiveSetWithTypeConversion 测试设置值时的类型转换和大小写处理
|
||||||
|
func TestMapCaseInsensitiveSetWithTypeConversion(t *testing.T) {
|
||||||
|
t.Run("Set with type conversion on existing lowercase key", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"config": map[string]int{
|
||||||
|
"timeout": 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键和字符串值设置 int 类型的 map
|
||||||
|
rfx.Set("Config.Timeout", "60")
|
||||||
|
|
||||||
|
configMap := m["config"].(map[string]int)
|
||||||
|
if configMap["timeout"] != 60 {
|
||||||
|
t.Errorf("Expected 60, got %v", configMap["timeout"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set nested value with multiple type conversions", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"data": map[string]any{
|
||||||
|
"stats": map[string]float64{
|
||||||
|
"rate": 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写路径和整数值设置 float64
|
||||||
|
rfx.Set("Data.Stats.Rate", 100)
|
||||||
|
|
||||||
|
dataMap := m["data"].(map[string]any)
|
||||||
|
statsMap := dataMap["stats"].(map[string]float64)
|
||||||
|
if statsMap["rate"] != 100.0 {
|
||||||
|
t.Errorf("Expected 100.0, got %v", statsMap["rate"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Set with exact case match updates existing key", func(t *testing.T) {
|
||||||
|
m := map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"name": "Alice",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rfx := New(&m)
|
||||||
|
|
||||||
|
// 使用大写键访问小写键,然后设置新字段
|
||||||
|
rfx.Set("User.age", 30)
|
||||||
|
|
||||||
|
// 验证在现有的小写键中添加了新字段
|
||||||
|
if userMap, ok := m["user"].(map[string]any); ok {
|
||||||
|
if userMap["age"] != 30 {
|
||||||
|
t.Errorf("Expected 30, got %v", userMap["age"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("Expected 'user' key to contain the new field")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
27
util.go
27
util.go
@ -209,9 +209,9 @@ func getValueByPath(v reflect.Value, p ...string) reflect.Value {
|
|||||||
|
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
v = tryStructField(v, key)
|
v = tryStructFieldValue(v, key)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
v = tryMapField(v, key)
|
v = tryMapFieldValue(v, key)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
// 尝试将 key 转换为索引
|
// 尝试将 key 转换为索引
|
||||||
idx, err := strconv.Atoi(key)
|
idx, err := strconv.Atoi(key)
|
||||||
@ -257,10 +257,10 @@ func lowercaseFirst(s string) string {
|
|||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryStructField 尝试获取 struct 字段,支持小写字段名自动转大写
|
// tryStructFieldValue 尝试获取 struct 字段,支持小写字段名自动转大写
|
||||||
// 1. 首先尝试原始字段名
|
// 1. 首先尝试原始字段名
|
||||||
// 2. 如果失败且首字母是小写,尝试首字母大写的版本
|
// 2. 如果失败且首字母是小写,尝试首字母大写的版本
|
||||||
func tryStructField(v reflect.Value, fieldName string) reflect.Value {
|
func tryStructFieldValue(v reflect.Value, fieldName string) reflect.Value {
|
||||||
// 首先尝试原始字段名
|
// 首先尝试原始字段名
|
||||||
field := v.FieldByName(fieldName)
|
field := v.FieldByName(fieldName)
|
||||||
if field.IsValid() {
|
if field.IsValid() {
|
||||||
@ -279,10 +279,21 @@ func tryStructField(v reflect.Value, fieldName string) reflect.Value {
|
|||||||
return reflect.Value{}
|
return reflect.Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryMapField 尝试从 map 中获取值,支持 string 键的小写字段名自动转大写
|
// tryMapFieldValue 尝试从 map 中获取值,支持 string 键的小写字段名自动转大写
|
||||||
// 1. 首先使用原始 key 尝试
|
// 1. 首先使用原始 key 尝试
|
||||||
// 2. 如果 map 的键类型为 string 且 key 首字母是大写,再尝试首字母小写的版本
|
// 2. 如果 map 的键类型为 string 且 key 首字母是大写,再尝试首字母小写的版本
|
||||||
func tryMapField(m reflect.Value, key string) reflect.Value {
|
func tryMapFieldValue(m reflect.Value, key string) reflect.Value {
|
||||||
|
actualKey := tryMapFieldKey(m, key)
|
||||||
|
if !actualKey.IsValid() {
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
return m.MapIndex(actualKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryMapFieldKey 尝试从 map 中获取实际存在的键
|
||||||
|
// 支持大小写不敏感查找
|
||||||
|
// 返回: 实际的键
|
||||||
|
func tryMapFieldKey(m reflect.Value, key string) reflect.Value {
|
||||||
if !m.IsValid() || m.Kind() != reflect.Map {
|
if !m.IsValid() || m.Kind() != reflect.Map {
|
||||||
return reflect.Value{}
|
return reflect.Value{}
|
||||||
}
|
}
|
||||||
@ -308,7 +319,7 @@ func tryMapField(m reflect.Value, key string) reflect.Value {
|
|||||||
// 尝试原始 key
|
// 尝试原始 key
|
||||||
val := m.MapIndex(mapKey)
|
val := m.MapIndex(mapKey)
|
||||||
if val.IsValid() {
|
if val.IsValid() {
|
||||||
return val
|
return mapKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果键类型是 string 且首字母大写,尝试首字母小写版本
|
// 如果键类型是 string 且首字母大写,尝试首字母小写版本
|
||||||
@ -317,7 +328,7 @@ func tryMapField(m reflect.Value, key string) reflect.Value {
|
|||||||
mapKey = reflect.ValueOf(lowercased)
|
mapKey = reflect.ValueOf(lowercased)
|
||||||
val = m.MapIndex(mapKey)
|
val = m.MapIndex(mapKey)
|
||||||
if val.IsValid() {
|
if val.IsValid() {
|
||||||
return val
|
return mapKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user