json.rawmessage 更适合动态过滤,因其缓存原始字节、按需解码,避免全量解析导致的性能损耗、精度丢失和 panic 风险;而 map[string]interface{} 强制解析所有字段,易引发浮点误差与内存膨胀。

为什么 json.RawMessage 比直接 map[string]interface{} 更适合动态过滤
因为 map[string]interface{} 会强制解析所有字段(包括嵌套数组、数字精度丢失、空值转 nil),而 json.RawMessage 把原始字节缓存下来,只在真正需要时才解码目标字段——既省 CPU,又保精度,还能跳过你根本不想碰的字段。
常见错误是:先用 json.Unmarshal 全量解析成 map[string]interface{},再递归删键,结果大 JSON 解析慢、内存涨、浮点数变 123.45000000000002,还容易 panic。
实操建议:
- 定义结构体时,对「可能被过滤」的字段用
json.RawMessage类型 - 用
json.Unmarshal一次解析到该结构体,不触碰未声明字段 - 后续按需对指定
json.RawMessage字段做二次解码或直接 byte 操作(如bytes.Contains判断是否存在某 key)
如何用 map[string]json.RawMessage 实现字段白名单过滤
这是最轻量、最可控的动态过滤方式:不预设 schema,只保留用户传入的 key 列表对应字段,其余全丢弃。
示例场景:API 接收 fields=uid,name,avatar 查询参数,只返回这三个字段的原始 JSON 值(含嵌套结构、类型不变)。
实操建议:
- 先用
json.Unmarshal将原始数据解到map[string]json.RawMessage - 遍历白名单
[]string{"uid","name","avatar"},从 map 中取对应json.RawMessage,忽略不存在的 key - 用
map[string]json.RawMessage构造新 map 并json.Marshal输出——天然保留原始格式和类型
注意:json.RawMessage 是 []byte 别名,不能直接赋值给 string;若需日志调试,用 string(raw) 转换,但别用于比较或拼接。
gjson 库做路径式过滤时,哪些写法会导致性能骤降
gjson.Get 看似方便,但如果在循环里反复调用 gjson.Parse(data).Get("users.#.name"),每次都会重新构建整个 AST,大数据量下比原生 json 包还慢。
实操建议:
- 对同一份数据做多次查询,先
gjson.Parse(data)一次,保存结果,再链式调用.Get - 避免用
#或*匹配大量元素后取全部字段(如items.#.detail.*),改用result.ForEach手动控制迭代边界 - 如果只要判断某个路径是否存在,用
result.Exists(),比Get().Exists()少一次 value 提取开销
兼容性提示:gjson 不支持修改或删除字段,纯读取;若需输出精简后 JSON,仍得把提取出的值手动组装回 map 或结构体再序列化。
用 encoding/json 的 Decoder.Token() 流式跳过不需要的字段
当 JSON 很大(比如 >10MB)、且只需其中几个顶层字段时,全量解析浪费内存。这时用流式 token 解析,遇到非目标 key 就调用 skip() 快速跳过对应值(包括嵌套对象/数组)。
实操建议:
- 维护一个目标 key 集合(
map[string]bool{"id":true,"title":true}) - 循环
dec.Token(),遇到json.ObjectKey时检查是否在集合中;不在则调用dec.Skip() - 遇到目标 key 后,用
dec.Token()读取其值(string/float64/etc),或用json.RawMessage捕获原始字节
容易踩的坑:dec.Skip() 只跳过「当前 token 对应的完整值」,必须确保你在 object key 之后、value 之前调用它;若误在 value 已开始读取后调用,会 panic。
流式处理没法回溯,也不适合随机访问路径,但它在「大 JSON + 少字段 + 低内存」场景下几乎是唯一靠谱的选择。











