what
|
1cc43562ba
|
docs: 把"该传值还是传指针"讲清楚, 代替新增 Ref 选项
起因是想给 New 加一个跳过深拷贝的开关。做出来之后对比发现,现有的
"传指针"已经在三个维度上都更好, 那个开关是多余的 API:
New(m, WithRef()) New(&m)
速度 47.5ns / 3 allocs 24.8ns / 1 alloc
map/slice 共享底层 是 是
struct 写回调用方 否 是
新增 API 一个选项 + 一套语义 零
WithRef 对 struct 还有个说不清的半吊子语义 —— 共享 map/slice 字段的底层数据,
但写不回 struct 本身。这种"部分穿透"很难讲明白也容易踩坑, 而 & 没有这个问题。
所以撤掉选项, 改为把这件事在文档里讲透。
README 新增"该传值还是传指针"一节, 给出实测对比:
10 键嵌套 map 传值 3227ns/119allocs 传指针 24.8ns/1alloc 130x
50 元素 slice 传值 594ns/ 6allocs 传指针 25ns/1alloc 24x
8 字段纯值 struct 传值 85.5ns/ 2allocs 传指针 25.5ns/1alloc 3.4x
判断标准就一句: 这份数据我会不会通过 R 去写? 写了穿透出去有没有问题?
只读或本来就想改原数据就传指针; 需要互不干扰的副本就传值(或传指针后 Scope)。
另附取不到地址的表达式该怎么落局部变量。
代码改动只剩一处无关的重构: 把 cloneForValueInput 里的浅拷贝抽成 shallowBox。
新增 TestPointerInputSharesAndSkipsClone 把 README 的承诺钉住:
传指针必须共享底层 map/slice、能写回 struct 变量、不触发深拷贝,
且 Scope 仍能在此基础上取到隔离副本。
|
2026-08-31 14:40:37 +08:00 |
|
what
|
6c6159f342
|
fix: Scope() 返回快实现, 不再把后续操作丢回逐次反射
Scope() 原来是 tv.slow().Scope(), 返回的是 *refx —— 基于 reflect 的慢实现。
功能完全正常, 只是它之后的每一次 Get/Set 都退回逐次反射, 这次重写的收益
在 Scope 之后全部消失。
Scope("A") 之后再 Get 144.6ns / 7 allocs -> 48.7ns / 2 allocs 2.97x
对照: 不经 Scope 直接 Get 59.0ns / 2 allocs
改为自己走 walk 定位、克隆、再包成 *rfx。克隆复用 cloneForValueInput,
与 New(值) 同一套逻辑: 含引用成分才递归深拷贝, 纯值类型逐字节复制。
这类问题很容易在重构里悄悄失守 —— 交回慢实现不会有任何功能异常, 只是慢 3 倍,
测试不看动态类型就发现不了。所以 TestScopeReturnsFastImpl 直接断言返回的是
*rfx, 并验证过有效: 改回 &refx{} 后该用例失败。
顺带确认了其余 slow() 调用点都不会把慢实现泄漏给调用方:
Append/Delete 返回 v 自身, Keys/StringMap*/Slice* 返回的是具体 Go 类型。
另加 Scope 的语义测试(含引用子树 / 纯值子树 / map 子树 / 无参数整体克隆,
都断言原数据未被改写)与 78 个对拍用例。
|
2026-08-31 14:29:06 +08:00 |
|
what
|
ca2a8f911a
|
perf: map 分段走 reflect, 离开后切回快路径
walk 逐跳调用 step, 而 step 的 map 分支每次都要 boxCopy —— map 元素不可寻址,
rfx 的表示又需要一个地址, 只能 reflect.New 复制一份。层级越深累积越多,
导致纯 map 路径比改造前的旧实现还慢, 是整个重写里唯一的性能回归。
改法要两头兼顾:
- 一路 reflect 走到底能省掉逐跳装箱, 但 map 后面的 struct 跳会丢掉偏移量
快路径、退回 FieldByName —— 实测 map→struct→struct 反而比逐跳装箱慢 5.8%
- 所以只在**连续的 map 段**内用 reflect, 一离开就装箱一次切回快路径
map→map→struct→struct 303.2ns/16a -> 205.6ns/10a 1.47x
map→struct→struct→map 309.0ns/16a -> 246.3ns/11a 1.25x
map→struct→struct 224.6ns/11a -> 140.3ns/ 6a 1.60x
纯 map 三层 300.9ns/20a -> 260.8ns/15a 1.15x (原 0.72x)
纯 map 单跳 120.0ns/ 8a -> 126.8ns/ 7a 0.95x
struct 三层(对照) 200.4ns/ 9a -> 59.6ns/ 2a 3.36x
单跳 map 仍慢 5%: 只有一跳时没有 boxCopy 可省, 而 valueAt 那次 reflect.NewAt
省不掉。其余形态全部转正。
两个实现上的坑, 都是测试抓出来的:
1. walkMapRun 发现已离开 map 段时, 那一跳已经从迭代器取走但还没用, 必须交还
给调用方。最初用 *it = save 回退, 结果**零分配快路径全线退化成 1 次分配** ——
通过指针写回会让逃逸分析认为 it 指向的内容可能逃逸, 进而判定 Get 的可变参数
切片逃逸, 波及所有路径, 连纯 struct 的路径都跟着中枪。改成把待处理的段
作为返回值交出去就好了。
2. 若干处清理: normalize 从 step 提到 walk(step 只有 walk 一个调用方);
移除 step 里已不可达的 Map 分支。
测试 252 个对拍用例覆盖 map 之后的各种后继类型与失败形态, 另加 19 个交界用例
专压"离开 map 段"那一跳。已验证有效: 把交还 pending 的那行改成丢弃后,
53 个用例失败。
|
2026-08-31 14:15:39 +08:00 |
|
what
|
ba0fd4b3bc
|
perf: Keys() 原生实现 map 分支, 字符串键不再绕 fmt.Sprint
Keys() 的 struct 分支早就是原生的了, map 分支一直委托给 refx, 而那边用的是
fmt.Sprint(k.Interface()) —— 要先把 key 装箱成 interface 再走一遍格式化。
对字符串键完全没必要。
Keys() 3 个字符串键的 map 193.3ns / 9 allocs -> 119.7ns / 5 allocs 1.61x
非字符串键仍旧走 fmt.Sprint: k.String() 对非字符串 Value 返回的是
"<int Value>" 这种占位串而不是键本身, 无脑替换会静默给出错误的键名。
按 m.Type().Key().Kind() 分支处理。
顺带把 normalize() 提到开头, 这样经指针或 interface 拿到的 map/struct 也能
走原生分支, 而不是掉进 slow()。
测试期间发现并修掉一处自己引入的差异: nil map 上旧实现返回 []string{},
我最初加的 IsNil 提前返回给出的是 nil。两者在 reflect.DeepEqual 和 JSON
序列化(`[]` vs `null`)上不同。MapKeys() 对 nil map 本来就返回空切片而不是
panic, 那个提前返回是多余的。
测试覆盖 11 种输入与旧实现对拍(string/具名 string/int/int64/float/bool 键、
空 map、nil map、struct、切片、嵌套 map), 外加经路径取到的 map、
interface 包着的 map、指针指向的 map, 以及非字符串键必须是真实键值的断言。
map 迭代顺序随机, 比较前排序。
|
2026-08-31 11:14:24 +08:00 |
|
what
|
a2c0c9783e
|
perf: New(值) 对不含引用成分的类型改走浅拷贝
New(值) 的语义是"改副本不影响调用方的原数据", 原来一律走 DeepClone 递归。
但对于不含引用成分的类型, 逐字节拷贝就已经完全独立了, 没必要递归。
在类型描述符里加 needsClone(构建期算一次, 不在热路径):
含指针/切片/map/interface/chan/func/unsafe.Pointer 即为真, 递归包含字段与
数组元素。字符串不算 —— 底层数组不可变, 共享是安全的。
New(纯值 struct) 145.0ns -> 85.5ns 1.70x
New(含引用 struct) 304.9ns 不变(深拷贝确实必要)
New(&ptr) 25.8ns 不变
注意 1.70x 而不是更多: New 还要做类型断言、normalizeInputValue、递归解引用、
类型校验和描述符查找, 省掉的只是深拷贝那一段(约 59ns)。
needsClone 必须在递归之前按 Kind 置位, 否则自引用类型会漏标: 类型图里的环
一定要经过某种间接(指针/切片/map), 先置位才能让递归回来的自引用类型读到
正确的值。测试里专门有一条 cNode{Next *cNode} 守这个。
判错的后果是静默的数据串改 —— 浅拷贝一个含引用的类型, 写入会穿透到调用方
的原数据, 不报错也不 panic。所以测试分两层:
1. needsClone 判定本身: 14 个用例, 含"引用藏在两层嵌套下"、"藏在数组元素里"、
自引用、time.Time(内含 *Location)等容易漏判的形态
2. 隔离语义: 7 个用例, 无论走哪条路都断言原数据未被改写
3. 与旧实现对拍: 5 种输入 x 5 个观察方法
已验证测试有效: 把 needsClone 强制为 false 后, 6 个隔离用例全部失败, 包括
藏在两层嵌套和数组元素里的引用。
|
2026-08-31 11:10:49 +08:00 |
|
what
|
cb84176381
|
docs: 补上 New 的解析流程,并用测试钉住 rfx 的大小
原来的流程文档只画了 Get 和 Set, New 在图里就是一个方框。但 New 的分支其实不少,
而且"指针模式 vs 值模式"是对外承诺的语义, 值得单独说清楚。
新增 docs/flow-new.svg 与对应章节, 覆盖四步: 快速分支 -> 解包 -> 解引用与校验
-> 按 isPtr 分岔。其中三处是使用者真正需要知道的:
1. 快速分支里 []R 不深拷贝也不包指针。这样 Raw() 才是 Slice kind、
Array() 才能取到里面的 R。
2. 传值比传指针贵约 60 倍(约 1518ns/44allocs vs 约 25ns), 因为要 DeepClone
递归复制整个对象图, 代价随对象规模增长。不需要副本语义就一律传指针。
顺带记下 DeepClone 的已知限制: 循环引用会栈溢出(CloneValue 没有已访问
集合), 这是旧实现就有的行为, 当前实现复用了它所以两边一致。
3. 为什么两条路最后都标 ptrRoot。漏掉它会连锁出问题: Raw() 返回值类型而不是
指针时, normalizeAccessorSlice 会把 []R 解成 []map 而不是 []*map ——
这个 bug 只在 []R 相关用例上才暴露得出来。
另外补 rfx_size_test.go: 文档里"24 字节"这个数字此前没有测试守着, 而 rfx 在
walk 循环里全程按值传递, 加字段会直接拖慢热路径。加了 ptrRoot 之后仍是 24 字节
(落在原有的对齐填充里), 用测试钉住, 以后加字段时会先失败。
同时修正: 源码对照表补上 New/newRfx 与 normalizeInputValue/DeepClone 的位置,
Get 章节的 rfx 字段列表补上 ptrRoot, 开头改成三个入口。
|
2026-08-31 11:02:00 +08:00 |
|
what
|
ac011cd5c5
|
docs: 更正 map 性能数据, 并增加内部流程文档
两部分改动。
一、更正 README 里关于 map 的性能结论(这是修正一个错误的说法)
原来写的是 "map 只快 1.25 倍", 但那个数字来自 Get("Meta","k") ——
第一跳 Meta 是 struct 字段, 走了快路径, 把第二跳 map 的损失盖了过去。
标成 "map 键" 会误导人。
补测纯 map 路径(根就是 map, 全程没有 struct 跳), 复测 10 轮结果稳定:
1 层 旧 120.7ns/8allocs -> 新 161.7ns/8allocs 0.75x
3 层 旧 314.4ns/20allocs -> 新 439.5ns/20allocs 0.72x
也就是说纯 map 场景当前实现比旧版慢约 28%, 不是变快。原因是内部表示为
"地址 + 类型描述符", 而 map 元素不可寻址, 每跳一次 map 都要 reflect.New
拷贝一份才能拿到地址; 旧实现直接持有 reflect.Value, 没这次拷贝。
表格改成分行列出两种场景, 并加了选型建议: 数据以 map[string]any 为主的
调用方, 这次重写没有收益, 反而略有退化 —— 收益全部集中在 struct 字段访问。
二、新增 docs/flow.md 与两张手写 SVG 流程图
说明 Get/Set 如何把路径逐段分派: 每段先 normalize 剥掉指针和 interface,
再按 Kind 进入 struct/slice/array 三条 unsafe 快路径、map 的 reflect 回退,
或直接拒绝。三色区分快路径 / 回退 / 拒绝, 把两件容易被忽略的事画了出来:
1. map 是唯一在 walk 循环内部就要堆拷贝的分支 —— 正是上面那个负收益的
直接原因, 图里一眼能看到。
2. Set 的 assignField 是四级递降, 第 ④ 级把复合类型整个交回 refx。
原因写在文档里: 指针字段要设置指向的值而不是替换指针、[]any 要逐元素
转换、map 可填充进 struct —— 重新实现必然出偏差, 是实际踩过的坑。
SVG 通过 img 引用时是隔离渲染(拿不到宿主页面的 CSS 变量和 currentColor),
所以配色和 prefers-color-scheme 明暗适配都烘在文件内部, 背景留透明。
README 性能章节开头加一行指向该文档, 正文只留结论。
|
2026-08-31 10:29:52 +08:00 |
|
what
|
d3216cb3b4
|
docs: 说明类型描述符缓存的生命周期与 reflect.StructOf 的注意事项
补充两个评审/接手时大概率会问到的问题:
1. 缓存什么时候清除 —— 永不清除, 正常情况下也不需要。key 是 reflect.Type,
程序里声明的类型是编译期确定的有限集合, 缓存大小 warmup 之后收敛。
附实测占用(1000 个类型约 0.6 MB), 并说明只增不删是有意设计:
描述符指针永久有效, 递归引用不需要生命周期管理。
2. 唯一的例外是调用方用 reflect.StructOf 在运行时动态造类型(典型是 ORM 按
查询字段拼扫描目标 struct)。实测 12 字段的动态类型每个约 3315 字节,
其中 reflux 占 59%、Go runtime 自己的类型元数据占 41% ——
后者同样永不回收, 所以这不是 reflux 特有的问题。
同时给出两条实践建议: 优先在上游收敛字段组合并缓存类型(reflect.StructOf
即使命中 runtime 缓存也要 2μs / 30 次分配); 以及注意字段顺序 ——
字段集合相同但顺序不同会产生不同的类型, 拼字段时遍历 map 会因为迭代顺序
随机而每次造出新类型, 那才是真正会导致无界增长的 bug。
|
2026-08-28 20:05:13 +08:00 |
|
what
|
05522809b9
|
docs: 修正 README 中会被渲染成删除线的波浪号
GFM 支持单波浪线删除线(~text~), 所以同一行出现两个 ~ 时,
'快 3~4 倍,访问器快 8~12 倍' 会把中间的 '4 倍,访问器快 8' 渲染成删除线。
表示数值范围的 ~ 统一改为 -。
|
2026-08-28 17:09:07 +08:00 |
|
what
|
659fc10bae
|
fix: 修正 Example 函数命名与输出注释位置,让 go vet / go test 恢复可用
go vet ./... 此前有 13 处失败, 导致 go test ./...(默认带 vet)根本跑不起来,
必须加 -vet=off。这是既存问题, 与实现改动无关。
命名问题(12 处): Example 函数名的前缀必须是包内真实存在的标识符。
ExampleStructMap_* -> ExampleR_* (包里没有 StructMap)
ExampleSet_* -> ExampleR_Set_* (Set 是 R 的方法, 不是包级函数)
ExampleAppend_* -> ExampleR_Append_* (同上)
ExampleSchema_Unmarshal -> ExampleSchema_unmarshalJSON
(Schema 没有 Unmarshal 方法,
这个例子演示的是 json.Unmarshal)
输出注释位置(1 处): ExampleR_Append_typeConversionError 的 // Output: 后面
还跟着一条注释, 违反"Output 必须是最后一个注释块"。因为格式不合法, Go 一直
没把它当输出例子比对 —— 里面写的期望输出其实是错的(与实际信息不符)也没被发现。
把注释挪到 defer 之前, 并把期望输出更正为真实的 panic 信息。
已验证: 故意改错期望输出现在会导致测试失败, 说明确实在比对了。
现在 go vet ./... 干净, go test ./... 无需 -vet=off 即可运行。
|
2026-08-28 17:02:56 +08:00 |
|
what
|
e594e206f0
|
test: 补上 addrOf 的测试覆盖
addrOf 是 unsafeptr.go 里唯一没有测试覆盖的函数。它的触发路径比较隐蔽:
interface 字段里装着一个 Accessor, 且它的 Raw() 返回**可寻址的非指针**值,
此时 fromReflect 才会走 CanAddr 分支。老实现 refx 的 Get 结果正是这种形态。
补测之后核心实现(rfx/typedesc/unsafeptr/path/generic)达到 100% 函数覆盖,
unsafe 代码逐个函数都有测试经过。
|
2026-08-28 16:56:08 +08:00 |
|
what
|
9cb3ea857e
|
fix: 补齐 Get[T] 的类型分支,避免静默返回零值
Get[T] 原本只处理 string/int/int64/bool/float64 五种,其余类型掉进 default
分支的 Any().(T) 断言。而断言**不做转换**,导致:
Get[int32](r, "Age") // 字段是 int(42) -> 静默返回 0
Get[[]string](r, "Tags") // 字段是 []any -> 静默返回 nil
Get[map[string]string](r, "M") // 字段是 map[string]any -> 静默返回 nil
而对应的访问器 r.Get("Age").Int32() 等返回的是正确的转换结果。
实测 8 个场景里 7 个静默给错数据 —— 不报错不 panic,是最难排查的一类问题。
补齐后 T 覆盖 valuex.Accessor 全部转换方法对应的类型(9 个标量 + 4 种切片
+ 6 种 map),每个分支都落到同名访问器上,转换语义完全一致。
五种快路径类型的零分配特性不受影响(新分支同样用 detachPath 切断逃逸)。
新增 TestGenericMirrorsAccessors: 23 个用例逐个比对 Get[T] 与同名访问器,
并额外断言结果不是零值,防止将来有人删掉分支又掉回 default。
|
2026-08-28 16:50:43 +08:00 |
|
what
|
a6283ac432
|
fix: 修正 fieldx 中过时的字段缺失测试并补齐 Required 覆盖
TestSchema_Generate_FieldNotFound 断言字段缺失时报错, 但用例里没有设置
Required: true。按 Field.Required 的文档语义, 只有 Required 为 true 时字段
缺失才算错误, 否则取到 nil 继续 —— 所以这个测试一直是失败状态, 应该是在
Required 引入之前写的, 加了 Required 之后没有同步更新。
修正: 给用例加上 Required: true, 让它真正测到报错分支。
另补 TestSchema_Generate_OptionalFieldNotFound 覆盖非必需分支 ——
Required 此前在整个包里没有任何测试覆盖。
|
2026-08-28 16:42:04 +08:00 |
|
what
|
5c9f8bd6e7
|
perf: 用类型布局缓存 + 指针偏移寻址重写热路径
将实验目录 experiment/fastx 的实现合入主实现,替换原来逐次反射的做法。
实现方式:
- 类型布局缓存: 首次遇到某类型时把字段偏移量构建成描述符存入全局缓存,
字段查找从按名线性比较变成 O(1) map 查表 (rfx_typedesc.go)
- 指针偏移寻址: 取字段时用 基址+偏移量 直接算地址, 不再构造中间 reflect.Value
(unsafeptr.go, 全部 unsafe 代码集中在这一个文件)
- 零分配路径解析: 路径按需切片遍历, 不再为每次 Get 分配临时切片 (path.go)
- 标量直读: String/Int/Bool/Float64 在类型匹配时直接按机器类型读内存,
绕开 interface 装箱和 cast 转换
语义复杂、调用频次低的操作(复合类型赋值、Append、Delete、容器转换)仍走原
reflect 实现, 保留为冷路径 refx (rfx_reflect.go), 避免重写引入行为偏差。
新增包级泛型函数 Get[T](r, path...), 按路径直接取出目标类型, 零内存分配。
语义与 r.Get(path...).Xxx() 严格等价, 不改动任何现有接口。
性能对比(同进程同数据, -benchmem -count=6 中位数):
- Get 嵌套 + String 150.6ns/7allocs -> 50.7ns/2allocs 2.97x
- Get 4 层深路径 234.1ns/10allocs -> 68.9ns/2allocs 3.40x
- Set 单层 65.8ns/2allocs -> 18.1ns/0allocs 3.64x
- Set 嵌套 122.1ns/3allocs -> 35.5ns/0allocs 3.44x
- Exists 127.4ns/5allocs -> 37.2ns/1alloc 3.42x
- 访问器 String 15.0ns/1alloc -> 1.3ns/0allocs 11.8x
- Get[string] 泛型直取 -> 29.9ns/0allocs 5.04x
两处不快: map 键访问 1.25x(map 无稳定布局, 仍走 reflect);
New 构造 0.67x(多一次描述符缓存查找, 一次性成本)。
内存安全: 未导出字段在读和写两处显式拦截 —— reflect.NewAt 构造的 Value
不带只读标记, 语言层面的导出规则保护在 unsafe 路径上失效, 必须自己拦。
不变式与评审要点见 unsafeptr.go 顶部注释。
测试: 新增 703 个用例全部通过, 含 go test -race。
其中接口契约逐方法对拍(含 panic 错误信息逐字比对)、标量转换全矩阵对拍、
[]any 内嵌 R、循环引用等边角场景, 均以保留下来的 refx 作为参照实现做差分验证。
合并过程中据此发现并修复 11 处行为偏差。
|
2026-08-28 16:37:04 +08:00 |
|
what
|
1f48635da3
|
fix: New()/Array() 正确处理 Nil 及已经是 R 的元素,不再 panic/重复包装
New(nil) 之前会 panic,现在返回 Nil(新增的 R 空实现单例,所有操作都是
安全的空操作/零值,不用调用方每次都判空)。New([]R{...}) 和 Array() 遇到
元素本身已经是 R(包括 Nil)时直接透传,不再重新拿 reflect.Value 包一层,
避免丢失原有的空值语义或类型信息。
|
2026-08-20 17:28:57 +08:00 |
|
what
|
3915bee7b8
|
feat: Slice方法支持JSON字符串自动解析
当Slice()方法遇到字符串类型且内容以'['开头时,会自动尝试JSON解析为切片。
这使得处理JSON格式的数组字符串更加便捷。
|
2026-01-12 15:04:42 +08:00 |
|
what
|
39da1d55dd
|
feat: 添加 Lookuper 接口和 MustLookup 方法
- 新增 Lookuper 接口,提供 Lookup 和 MustLookup 两种路径查找方式
- Accessor 接口继承 Lookuper,保持向后兼容
- MustLookup 在路径不存在时返回 Nil 访问器,简化调用代码
- 更新 fieldx.Schema 使用 Lookuper 接口,支持更灵活的数据源
- 添加 Required 字段选项,控制字段不存在时的行为
|
2026-01-05 16:59:31 +08:00 |
|
what
|
b73099d205
|
refactor: 改进错误处理,使用预定义错误替代硬编码字符串
- fieldx.Schema.Generate: 添加 panic 捕获机制,优雅处理 reflux.New 的异常
- fieldx.Schema.Generate: 支持 any 类型参数,增强通用性
- reflux.New: 使用预定义错误(ErrInvalidValue, ErrTargetNilPointer, NewErrUnsupportedTargetType)替代硬编码错误字符串
- 提高错误信息的一致性和可读性
|
2025-12-26 21:15:31 +08:00 |
|
what
|
a6d3e34e53
|
feat: 添加 fieldx 包,支持基于 Schema 的对象生成
新增 fieldx 包,提供基于预定义 Schema 生成 map[string]any 对象的功能。
主要特性:
- 支持固定值字段 (string)、字段引用 (field) 和嵌套对象 (object)
- 支持点号分隔的嵌套路径访问 (如 "user.name")
- 提供多种 Schema 创建方式 (JSON、Map、编程方式)
- 完善的错误处理和文档示例
|
2025-12-26 20:30:25 +08:00 |
|
what
|
d233fc319b
|
feat: 支持嵌套 R 和 valuex.Accessor 对象的路径访问
- 添加 unwrapAccessor 统一处理 Accessor 解引用
- 添加 deref/derefWithAccessor/derefValue 封装重复的解引用逻辑
- getValueByPath 支持穿透 R/Accessor 对象访问嵌套路径
- setNestedValue 支持在 R/Accessor 对象中设置嵌套值
- CloneValue 和 cloneElement 正确克隆 Accessor 底层值
- 支持在 map/struct/slice 中嵌套 R 对象并通过路径访问
- 添加完整测试覆盖 map/struct/slice/深度嵌套等场景
- 减少 38 行重复代码,提高代码可维护性
|
2025-12-24 16:37:12 +08:00 |
|
what
|
c62b07a119
|
refactor: 简化 normalizeInputValue 类型匹配逻辑
由于 R 接口继承 valuex.Accessor,无需单独处理 R 类型
使用 valuex.Accessor 统一处理即可,减少冗余代码
|
2025-12-16 16:19:29 +08:00 |
|
what
|
baad0cadfc
|
feat: 完善 normalizeInputValue 支持 valuex.Accessor 并优化代码
- 在 valuex.Accessor 接口中添加 Raw() 方法
- normalizeInputValue 支持 valuex.Accessor 和 []valuex.Accessor 类型
- 提取 normalizeAccessorSlice 泛型函数消除重复代码
- 使用 switch 语句替代 if-else 链提高可读性
- 添加相关测试用例确保功能正确性
|
2025-12-16 16:14:46 +08:00 |
|
what
|
7d17293df3
|
feat: 增强错误处理与示例
|
2025-12-09 20:06:56 +08:00 |
|
what
|
72d670be0b
|
feat: 增强 map 大小写不敏感支持和函数重命名
- 新增 tryMapFieldKey 函数,返回 map 中实际存在的键(支持大小写转换)
- 优化 setFieldValue 方法,使用 tryMapFieldKey 查找已存在字段并更新
- 优化 setNestedValue 方法,修复嵌套 map 大小写处理和 interface 包装问题
- 重命名函数以提高代码清晰度:
- tryStructField → tryStructFieldValue
- tryMapField → tryMapFieldValue
- tryMapFieldWithKey → tryMapFieldKey
- 新增 rfx_map_case_test.go 包含 16 个测试用例,覆盖基本、嵌套、边界等场景
|
2025-12-09 16:55:13 +08:00 |
|
what
|
fbba6f9a30
|
feat: 增强 nil 值处理能力
主要改进:
- 简化 setValue 方法中的 nil 值处理逻辑,统一设置为目标类型的零值
- 修复 setFieldValue 方法中 map 类型设置 nil 值时的问题
- 新增完整的 nil 值设置测试,覆盖所有 Go 基础类型和复合类型
测试覆盖:
- 指针、接口、切片、map、channel、func 等可空类型设置为 nil
- int/uint 系列、float 系列、bool、string 等基础类型设置为零值
- 嵌套结构体和 map 中的 nil 值设置
|
2025-12-09 15:11:18 +08:00 |
|
what
|
6e3593f188
|
feat: 优化 setValue 方法支持 nil 值处理
改进 setValue 方法在处理 nil 值时的行为:
- 对于可空类型(指针、接口、切片、map 等)设置为零值而非失败
- 对于基础类型(int、string、bool 等)继续执行 cast 转换逻辑
- 修复 val 无效时调用 val.Type() 的潜在空指针问题
|
2025-12-09 14:56:37 +08:00 |
|
what
|
2b7ac003a8
|
feat: Accessor 支持
|
2025-12-08 17:25:44 +08:00 |
|
what
|
bacec92841
|
feat: Append 方法支持 R 接口包装的指针类型
增强 setValue 函数,支持将 R 接口包装的指针值追加到非指针类型的切片中。当源值是指针但目标不是指针时,自动解引用后再赋值,使得 New(&item) 可以追加到 []Item 类型的切片。同时统一使用 Raw() 方法替代 Value() 方法名。
|
2025-12-08 17:24:45 +08:00 |
|
what
|
f5f261e541
|
feat: R 接口支持 JSON 序列化和反序列化
实现功能:
- R 接口继承 json.Marshaler 和 json.Unmarshaler 接口
- MarshalJSON(): 将 R 实例序列化为 JSON 字节数组
- UnmarshalJSON(): 从 JSON 字节数组反序列化到 R 实例
核心特性:
- 支持 struct、map、slice 等所有类型的 JSON 序列化
- 支持嵌套结构的序列化和反序列化
- 自动智能类型转换(如 JSON 数字 float64 -> int/int64)
- 可以对嵌套字段单独序列化(如 rfx.Get("Address"))
测试覆盖:
- TestJSONMarshal: 测试各种类型的序列化
- TestJSONUnmarshal: 测试各种类型的反序列化
- TestJSONRoundTrip: 测试序列化和反序列化的往返一致性
文档更新:
- 在 README 特性列表中添加 JSON 序列化说明
- 新增"JSON 序列化和反序列化"章节
- 包含完整的使用示例和最佳实践
- 说明使用场景:API通信、配置持久化、数据传输、缓存、消息队列等
|
2025-12-08 17:22:05 +08:00 |
|
what
|
a6a936c07c
|
feat: 增强 map 类型转换的容错能力
优化了所有 map 类型转换方法,采用三层转换策略:
1. 优先使用 cast 库进行类型转换
2. cast 失败后,通过 Keys() 和 Get() 进行自定义转换
3. 转换失败时提供完整的错误信息(包含 cast 错误和自定义转换错误)
优化的方法:
- StringMapString()
- StringMapStringSlice()
- StringMapBool()
- StringMapInt()
- StringMapInt64()
- StringMap()
这使得 struct 类型也能成功转换为 map 类型,增强了类型转换的灵活性。
|
2025-12-08 11:16:44 +08:00 |
|
what
|
9e1bc55a8c
|
feat: 增强类型转换能力和代码重构
核心改进:
1. 新增 normalizeInputValue 函数
- 统一处理输入值的规范化,支持 R 接口、[]R 切片、reflect.Value
- 避免重复封装,提升性能和类型安全性
2. 重构 getValueByPath 为独立函数
- 从 rfx 方法提取为独立函数,提高代码复用性
- 更好的职责分离,便于维护和测试
3. 显著增强 setValue 方法的类型转换能力
- 支持切片类型转换:[]any -> []T,自动转换每个元素
- 支持结构体类型转换:map -> struct 或 struct -> struct,按字段名匹配
- 保持指针切片的引用语义,避免不必要的对象复制
4. 新增 tryMapField 函数
- 支持 Map 键名的大小写不敏感访问
- 首字母大写的键会自动尝试小写版本(如 "Host" -> "host")
5. 新增 lowercaseFirst 辅助函数
- 用于首字母小写转换,配合 Map 键名查找
6. 更新测试用例
- 新增指针切片的使用示例
- 展示 []any 包含指针元素的场景
7. 文档全面更新
- 新增"高级类型转换"章节,详细说明切片、结构体、指针切片等转换
- 更新特性列表,突出增强的类型转换能力和 R 接口集成
- 补充 Map 大小写不敏感访问的说明
影响范围:
- reflux.go: 使用 normalizeInputValue 统一输入处理
- rfx.go: 重构并增强 setValue、getValueByPath 等核心方法
- util.go: 新增多个辅助函数,代码行数增加 160 行
- rfx_example_test.go: 新增指针切片测试用例
向后兼容:完全兼容现有 API,仅增强内部实现和类型转换能力
|
2025-12-05 11:12:11 +08:00 |
|
what
|
243d1ffac8
|
feat: 在 rfx.String() 中,先尝试用 cast.ToStringE 将当前值转为字符串,如果失败则增加了一个兜底逻辑:尝试使用 json.Marshal 将当前值序列化为 JSON 字符串并返回
|
2025-12-04 14:09:23 +08:00 |
|
what
|
b373dde7f7
|
feat: 支持切片追加及 -1 头部插入
|
2025-12-04 11:28:18 +08:00 |
|
what
|
cbe079ddcd
|
feat: 支持反射设置指针类型字段
- 如果目标字段是指针类型,它会尝试直接赋值(如果类型兼容),或创建一个新指针并递归设置其指向的值。
- `rfx_example_test.go` 中新增了测试用例,以验证对 `*string` 等指针字段的设置功能。
|
2025-12-04 11:25:44 +08:00 |
|
what
|
f9e7f8e781
|
fix: 修复深度克隆时 map/slice 中基本类型值被错误存储为指针的问题
- 提取 cloneSequence 函数统一处理 slice/array 克隆
- 提取 cloneElement 函数处理单个元素克隆
- 提取 needsDeepClone 函数判断是否需要深度克隆
- 减少代码重复,提高可维护性
为 ExampleNew_withValue* 系列测试添加 New() 后的输出,
清晰展示深度克隆后的初始状态和值类型正确性。
|
2025-12-03 10:20:15 +08:00 |
|
what
|
8fbc859e74
|
[feat] README.md 更新
|
2025-12-02 21:14:16 +08:00 |
|
what
|
97d41fa6d8
|
feat: 完善 New 方法支持指针和值参数,新增 interface 类型支持
- 支持指针模式: New(&data) - 直接修改原始数据
- 支持值模式: New(data) - 创建深度克隆,不影响原始数据
- 新增 interface{} 类型支持,自动解析到实际类型
- 对 map 和 slice 等引用类型也进行完全深度克隆
- 新增 9 个单元测试覆盖指针/值/interface 场景
- 新增 9 个示例测试展示各种用法
- 所有测试通过,保持性能
- 更新 README.md 详细说明指针/值传递的区别
- 新增 interface 类型使用示例
- 新增泛型数据处理使用场景
- 更新注意事项说明
- 使用递归解引用处理 interface 和指针类型
- 利用 DeepClone 确保引用类型的完全独立
- 保持向后兼容,现有代码无需修改
|
2025-12-02 21:08:33 +08:00 |
|
what
|
d9f178020a
|
first commit
|
2025-12-02 19:52:29 +08:00 |
|