多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

lo 库 Map 过滤实战:OmitBy 源码解析、类型保留机制与 Pick 系函数对照

lo 库 Map 过滤实战:OmitBy 源码解析、类型保留机制与 Pick 系函数对照 lo 库 Map 过滤实战OmitBy 源码解析、类型保留机制与 Pick 系函数对照【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo本文围绕 lo 库基于 Go 1.18 泛型的 Lodash 风格 Go 工具库核心包中的OmitBy函数展开讲解如何用一条断言函数反向过滤 map 键值对、剔除满足条件的条目并保留原 map 的类型同时对照PickBy、OmitByKeys、OmitByValues、OmitByErr等 Pick/Omit 家族成员结合 map.go 源码与 map_test.go 测试用例说明它们在数据清洗、配置脱敏、权限裁剪等场景下的实战用法与底层实现原理。一、OmitBy 是什么OmitBy是 lo 核心包中面向 map 的反向过滤工具它遍历输入 map对每一对键值调用断言函数predicate凡是断言返回true的条目都会被剔除返回一个只包含未被命中键值对的新 map。这与PickBy保留命中的条目正好语义互补一个取反、一个取正。lo 库文档对其定义只有一句话返回与输入同类型same type的 map并排除满足断言的条目。它的函数签名来自 docs/data/core-omitby.md frontmatterfunc OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map三个类型参数各有职责K comparable键类型必须是可比较类型Go map 键的硬性要求V any值类型任意类型均可无需可比较Map ~map[K]V底层类型为map[K]V的类型约束~波浪号表示底层类型匹配这是返回值能保留自定义 map 类型的关键。predicate接收(key, value)两个参数并返回bool——注意它与只接收单个元素的一元断言不同map 过滤天然携带键的信息因此可以基于键、值或二者的组合做判断。文档给出的最小示例m : lo.OmitBy( map[string]int{foo: 1, bar: 2, baz: 3}, func(key string, value int) bool { return value%2 1 }, ) // map[string]int{bar: 2}流程foo1、baz3为奇数断言命中被剔除bar2未命中得以保留最终得到map[string]int{bar: 2}。二、源码实现逐行拆解OmitBy的实现位于 map.go#L167-L177// OmitBy returns same map type filtered by given predicate. // Play: https://go.dev/play/p/EtBsR43bdsd func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map { r : make(Map, len(in)) for k, v : range in { if !predicate(k, v) { r[k] v } } return r }实现只有寥寥几行却有四个值得注意的细节预分配容量make(Map, len(in))直接以输入 map 的长度预分配避免扩容也提前声明了返回值是全新的 map不会修改输入。纯函数式只读遍历、无副作用输入 map 原封不动符合函数式工具库的一贯风格。取反逻辑源码用if !predicate(k, v)完成命中即剔除的语义与PickBymap.go#L115-L123的if predicate(k, v)形成镜像对照——两函数其余逻辑完全一致仅保留条件取反。类型保留返回类型是Map而非map[K]V这是same map type承诺的落点。为什么叫same type~底层类型约束的威力原生map[K]V类型无法直接接收自定义类型。lo 用Map ~map[K]V这样的类型集type set约束让任何底层类型为map[K]V的具名类型都能作为输入与返回值。例如type myMap map[string]int before : myMap{: 0, foobar: 6, baz: 3} after : OmitBy(before, func(key string, value int) bool { return false }) // after 的类型是 myMap而非 map[string]int这一点在测试 map_test.go#L406-L414 中有专门用例通过is.IsType(after, before, type preserved)断言验证。这意味着你可以把OmitBy用在项目自定义的 map 别名类型上返回值无需再手动转换。与 PickBy 的对照函数保留条件语义PickBypredicate(k, v) true保留命中项OmitBypredicate(k, v) false剔除命中项源码实现map.go#L115-L123 与 map.go#L167-L177唯一的区别就是这一处!因此二者在代码库中常成对出现选用哪一个取决于写起来更像自然语言想表达只保留满足条件的用PickBy想表达去掉满足条件的用OmitBy。三、OmitBy 家族Err、Keys、Values 变体lo 围绕按条件剔除还提供三个同族函数全部定义在 map.go 中覆盖断言可出错按键剔除按值剔除三种场景。3.1 OmitByErr支持返回错误的断言func OmitByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error)位于 map.go#L181-L193。与OmitBy的唯一区别是断言签名变为(bool, error)一旦断言返回非 nil 错误函数立即中止并返回(nil, err)错误优先传播不继续遍历仅当全部条目判断完毕且无错误时才返回结果 map。适用于断言本身可能失败的场景例如从外部系统查询后才决定是否剔除result, err : lo.OmitByErr(userMap, func(key string, v User) (bool, error) { active, err : isActive(v.ID) // 可能出错 if err ! nil { return false, err } return !active, nil // 剔除不活跃用户 }) if err ! nil { // 处理错误result 为 nil }测试 map_test.go#L417-L515 覆盖了按奇偶剔除空 map特定键出错时返回错误全剔除全保留五类场景其中错误用例断言返回的 map 为nilis.Nil(got)印证了出错即返回 nil map的行为。3.2 OmitByKeys按键列表剔除func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map位于 map.go#L195-L206。不需要断言直接给出一组要删除的键r1 : OmitByKeys(map[string]int{foo: 1, bar: 2, baz: 3}, []string{foo, baz, qux}) // map[string]int{bar: 2}实现分两步先把输入 map 完整拷贝进新 mapr[k] v再对keys逐个执行delete(r, keys[i])。因此键不存在于 map 中也不会报错——delete对不存在的键是空操作如上例中的qux被静默忽略仍然不修改输入 map只在拷贝上删除。测试见 map_test.go#L517-L538。它的典型用途是字段脱敏已知敏感字段名列表如password、token一条调用即可从响应 map 中剔除。3.3 OmitByValues按值列表剔除func OmitByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map位于 map.go#L208-L221。注意这里的值类型约束收紧为V comparable因为需要把值作为 map 键去查重r1 : OmitByValues(map[string]int{foo: 1, bar: 2, baz: 3}, []int{1, 3}) // map[string]int{bar: 2}实现借助 lo 的Keyify工具slice.go#L830-L840先把待剔除的值列表转成map[T]struct{}集合再遍历输入 map命中集合中的值即剔除seen : Keyify(values) // []int{1,3} - map[int]struct{}{1:{},3:{}} for k, v : range in { if _, ok : seen[v]; !ok { r[k] v } }使用 set 集合把值是否在列表中的判定从 O(n) 查找降为 O(1) 哈希查找。典型场景是按枚举值批量清理状态如剔除所有状态为已删除/已归档的条目。测试见 map_test.go#L540-L561。四、与 Pick 系函数及 FilterKeys/FilterValues 的辨析文档 frontmatter 中列出了OmitBy的六个 similarHelpersPickBy、OmitByKeys、OmitByValues、PickByKeys、PickByValues、FilterKeys、FilterValues、OmitByErr。它们在功能边界上有清晰区分函数判定依据返回类型语义PickBy键值断言同类型 map保留命中项OmitBy键值断言同类型 map剔除命中项PickByKeys/OmitByKeys键列表同类型 map保留 / 剔除指定键PickByValues/OmitByValues值列表同类型 map保留 / 剔除指定值OmitByErr/PickByErr可出错断言map error出错即中止返回错误FilterKeys/FilterValues键值断言[]K/[]V切片把 map 过滤成键/值切片关键差异点Keys/Values 变体不需要闭包纯数据驱动可提前把剔除清单序列化或配置化FilterKeysmap.go#L474-L487与FilterValuesmap.go#L489-L502虽然断言签名相同但返回的是切片而非 map——文档注释明确它们是lo.Filter与lo.Keys/lo.Values的混合体a mix of lo.Filter() and lo.Keys()。需要留下什么键或值时用它们需要留下完整的键值结构时用OmitBy/PickBy。选择建议想删掉命中项、保留其余键值结构 →OmitBy想保留命中项 →PickBy断言可能失败 →OmitByErr只按键删、不写闭包 →OmitByKeys只按值删 →OmitByValues只要键列表或值列表 →FilterKeys/FilterValues。五、实战三个典型场景5.1 按值过滤剔除所有偶数条目m : lo.OmitBy( map[string]int{foo: 1, bar: 2, baz: 3}, func(key string, value int) bool { return value%2 0 }, ) // map[string]int{foo: 1, baz: 3}5.2 基于键的判断过滤内部字段userData : map[string]any{ id: 1, name: alice, password: secret, token: abc123, } safe : lo.OmitBy(userData, func(key string, _ any) bool { return key password || key token }) // map[string]any{id: 1, name: alice}配合V any的值类型约束OmitBy可直接作用于异构的map[string]any是接口返回前做敏感字段脱敏的轻量方案。5.3 类型保留在自定义 map 类型上使用type userMap map[string]User active : lo.OmitBy(users, func(_ string, u User) bool { return !u.Active }) // active 仍是 userMap 类型基于Map ~map[K]V约束项目中的具名 map 类型无需手动转换即可传参并保持返回类型测试中的 type preserved 用例map_test.go#L406-L414正是对这一能力的回归保护。六、注意事项与边界行为输入 map 不会被修改所有 Omit 变体都是拷贝后操作可放心对原始数据重复调用不同条件结果新 map 与输入相互独立修改返回 map 不影响原 mapmap 遍历顺序不确定Go 语言规定 map 迭代顺序随机OmitBy对断言的调用顺序同样不确定——断言必须是纯函数无副作用、与顺序无关测试注释也明确说明wantCallCount为 0 是因为map iteration order is not deterministicmap_test.go#L426空 map 与全剔除输入为空 map 时返回空 map断言恒为true时返回空 map恒为false时返回完整拷贝这些边界在 TestOmitByErr 的用例中均有覆盖错误语义OmitByErr一旦遇错立即返回(nil, err)不会返回部分结果调用方需同时判 err 和结果。七、小结OmitBy是 lo 核心 map 工具中最常用的反向过滤入口一条(key, value) - bool断言即可精确剔除命中项Map ~map[K]V约束让返回类型与原类型保持一致。结合OmitByErr可出错断言、OmitByKeys按键删除、OmitByValues按值删除以及语义互补的PickBy家族开发者可以在不修改原始数据的前提下以几行声明式代码完成大部分 map 过滤、脱敏与裁剪工作。如需进一步阅读全部实现map.go测试用例map_test.go核心包 map 工具总览docs/docs/core/map.md相关文档OmitByErrdocs/data/core-omitbyerr.md、OmitByKeysdocs/data/core-omitbykeys.md、OmitByValuesdocs/data/core-omitbyvalues.md、PickBydocs/data/core-pickby.md【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表