gjson.Get()比json.Unmarshal快得多,因其不解析整个JSON、不走反射、不构建Go值,仅在字节流上按路径扫描并切片返回原始片段,零分配、无类型转换、不递归。

gjson 不走反射,也不解析整个 JSON,它直接在字节流上做路径匹配——所以快,也所以不能改、不能序列化、不能验证结构。
gjson.Get() 为什么比 json.Unmarshal 快得多
因为 gjson.Get() 根本不把 JSON 解成 Go 值,它只扫描原始 []byte 或 string,按点号路径逐段跳过无关字段,找到目标值的起始和结束位置,再切片返回原始 JSON 片段。整个过程零内存分配(除结果字符串)、无类型转换、不递归下降。
这意味着:
- 你传进去的是什么编码,它就按什么解码——比如含 UTF-8 BOM 的 JSON 会直接失败,
gjson.Get()不做任何预处理 - 如果路径写错(比如
"user.profile.name"但profile是null),它就安静地返回空Result,不会 panic,也不会告诉你哪一级断了 - 它不关心字段类型:数字、布尔、字符串、null 全部统一用
Result.String()或Result.Num拿,但Result.Num对非数字字段返回 0,且不报错
路径里带数组索引或通配符时的常见误判
像 "users.0.name" 这种写法看着直观,但实际行为依赖 JSON 原始结构是否严格匹配:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
-
"users.0"要求users是数组,且至少有 1 个元素;如果users是null或对象,gjson.Get()直接返回空结果,不提示“不是数组” -
"users.#.name"匹配所有users数组元素的name字段,但返回的是一个Result切片([]gjson.Result),得用result.Array()显式取;直接调result.String()只会返回第一个匹配项的字符串形式 - 路径中含点号的 key(如
"fav.movie")必须写成"fav.movie",否则会被当成两级嵌套;反斜杠要双写:"fav\.movie"才能在 Go 字符串字面量里生效
Result.String() 和 Result.Exists() 的隐性陷阱
Result 是个轻量结构体,字段都是只读缓存,但它的方法行为容易误导:
-
result.String()总是返回字符串,哪怕原始值是true、42或null——前者变"true",后者变"null";真要区分类型,得先看result.Type -
result.Exists()只判断该路径对应位置是否有合法 JSON 值(非null、非缺失),但它不检查类型;"age"存在但值为"not a number"时,result.Exists()仍返回true,而result.Num是 0 -
result.Raw是原始字节切片,指向输入 JSON 的某一段;如果输入是局部变量字符串,且你把它传给其他 goroutine 长期持有,要注意逃逸和生命周期——Raw不会拷贝数据
什么时候不该用 gjson
当你要做的事超出“单次读取几个字段”的范围,gjson 就开始暴露短板:
- 需要修改 JSON?不行,得换
sjson.Set(),它是另一套逻辑,不共享gjson.Result - 要校验字段必填、类型约束、枚举值合法性?gjson 不提供 schema 验证,得自己写逻辑或切回
json.Unmarshal+ struct - JSON 很大但只读一次,且后续还要反复查不同路径?gjson 每次
Get()都重新扫描,不如一次性json.Unmarshal成 struct,再用字段访问 - 字段名来自用户输入(比如 API 查询参数),且没做白名单过滤?路径注入风险真实存在——
gjson.Get(json, userInput)可能被构造为"$.constructor.prototype.pollute=1"类似攻击(虽然 gjson 不执行 JS,但路径失控仍可能绕过业务逻辑)
最常被忽略的一点:gjson 的“快”,只对单次、少量、已知路径的读取成立;路径动态拼接、嵌套过深、或高频循环调用 Get(),反而因重复扫描拖慢整体性能。

















