news 2026/10/10 2:00:07

JMESPath 查询语言与 go-jmespath 实战:在 Go 中解析、过滤与转换 JSON 数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JMESPath 查询语言与 go-jmespath 实战:在 Go 中解析、过滤与转换 JSON 数据
  • 游戏开发
  • 云原生

【免费下载链接】agones

Dedicated Game Server Hosting and Scaling for Multiplayer Games on Kubernetes

项目地址:https://gitcode.com/gh_mirrors/ag/agones
点击查看免费下载

go-jmespath 是 JMESPath 查询语言在 Go 语言下的完整实现,它接收一份 JSON 文档与一条 JMESPath 表达式,并将其转换为另一份 JSON 文档。本文以 vendor/github.com/jmespath/go-jmespath/README.md 为主体,结合该库在仓库中的完整源码(词法分析、语法分析、解释执行三个阶段)与依赖关系,系统讲解其核心 API、表达式语法、内置函数、错误处理与预编译优化。读完本文,你将能够在自己的 Go 服务中直接使用jmespath.Search/Compile对任意 JSON 数据做字段提取、数组投影、条件过滤、排序聚合等操作,并理解其在 Agones 仓库中作为 AWS SDK 间接依赖的引入背景。

一、go-jmespath 是什么:面向 JSON 的查询语言实现

JMESPath 是一种声明式查询语言,专门用于描述如何从 JSON 文档中提取和变换元素。go-jmespath 即其 Go 语言实现:输入一份 JSON 文档和一条 JMESPath 表达式,输出由表达式变换后的另一份 JSON 文档。其核心接口极其精简——只需一个函数即可完成全部工作。

在 Agones 仓库中,该库以v0.4.0版本出现在 go.mod 第 99 行,并被标注为// indirect(间接依赖),它由github.com/aws/aws-sdk-go v1.44.176(同为间接依赖)带入 vendor 目录,具体说明可见 vendor/modules.txt 中# github.com/jmespath/go-jmespath v0.4.0一节。虽然 Agones 自身业务代码并未直接调用它,但理解这一通用 JSON 查询能力,对任何需要处理嵌套 JSON 的 Go 服务都有直接参考价值。

二、快速上手:Search函数与第一个查询

go-jmespath 的使用方式极其简单,唯一需要掌握的入口函数是jmespath.Search(expression, data)。该函数接受两个参数:

  • expression:JMESPath 表达式字符串;
  • data:interface{}形式的 JSON 数据(通常由json.Unmarshal得到)。

返回值为查询结果与错误。README 中的经典示例演示了嵌套字段的取值:

import "github.com/jmespath/go-jmespath" var jsondata = []byte(`{"foo": {"bar": {"baz": [0, 1, 2, 3, 4]}}}`) // 你的数据 var data interface{} err := json.Unmarshal(jsondata, &data) result, err := jmespath.Search("foo.bar.baz[2]", data) // result = 2

对输入数据{"foo": {"bar": {"baz": [0, 1, 2, 3, 4]}}}求值表达式foo.bar.baz[2],得到结果2。这里foo.bar.baz是逐级取字段的链式访问,[2]是数组下标访问,两者组合完成了"钻取"到数组内部指定元素的能力。

从实现层面看,包级函数Search在 vendor/github.com/jmespath/go-jmespath/api.go 中定义为:先调用NewParser()解析表达式为 AST,再调用newInterpreter()创建解释器,最终由intr.Execute(ast, data)完成求值。也就是说,一次Search调用内部隐含了"解析 + 解释执行"两个完整阶段。

三、表达式能力进阶:投影、提取与条件过滤

README 强调:"JMESPath 语言能做的远不止从列表中选取一个元素",并给出了三组进阶示例,它们分别展示了取子文档、数组投影和条件过滤三类典型场景。

3.1 提取子文档

var jsondata = []byte(`{"foo": {"bar": {"baz": [0, 1, 2, 3, 4]}}}`) var data interface{} err := json.Unmarshal(jsondata, &data) result, err := jmespath.Search("foo.bar", data) // result = { "baz": [ 0, 1, 2, 3, 4 ] }

表达式foo.bar不再只是取出标量,而是将整个嵌套子对象{"baz": [0, 1, 2, 3, 4]}作为结果返回——这正是 JMESPath"将 JSON 变换为另一份 JSON"的直观体现。

3.2 数组投影(Projection)

var jsondata = []byte(`{"foo": [{"first": "a", "last": "b"}, {"first": "c", "last": "d"}]}`) var data interface{} err := json.Unmarshal(jsondata, &data) result, err := jmespath.Search("foo[*].first", data) // result = [ 'a', 'c' ]

foo[*].first中的[*]是投影语法:它遍历foo数组的每个元素,对每个元素求.first字段,最终聚合为一个新数组['a', 'c']。投影是 JMESPath 最具代表性的能力,也是后续?过滤器、|管道的语法基础。

3.3 条件过滤(Filter)

var jsondata = []byte(`{"foo": [{"age": 20}, {"age": 25}, {"age": 30}, {"age": 35}, {"age": 40}]}`) var data interface{} err := json.Unmarshal(jsondata, &data) result, err := jmespath.Search("foo[?age > `30`]", data) // result = [ { age: 35 }, { age: 40 } ]

表达式foo[?age > \30`]使用?引入过滤条件:保留age大于字面量30(注意 JMESPath 中数字字面量必须用反引号包裹)的元素。输出结果自动收敛为满足条件的两个元素[ { age: 35 }, { age: 40 } ]`,这正是按需筛选数据子集的标准写法。

说明:README 原文中第三个示例存在一处笔误(调用括号书写不完整),本文已按正确的jmespath.Search("foo[*].first", data)形式呈现,语义与原文一致。

这些语法糖在解释器中均有对应的实现载体:interpreter.go提供了filterProjectionWithReflection(过滤投影)、projectWithReflection(普通投影)、sliceWithReflection(切片)、flattenWithReflection(扁平化)等求值函数,见 vendor/github.com/jmespath/go-jmespath/interpreter.go。

四、预编译查询:Compile与MustCompile

如果你需要针对同一份数据执行多次查询,或想复用同一表达式求值于多条数据,README 建议预先编译表达式。这一点在高频调用的服务端场景下尤其重要,可以避免每次搜索都重复进行词法与语法解析。

var jsondata = []byte(`{"foo": "bar"}`) var data interface{} err := json.Unmarshal(jsondata, &data) precompiled, err := Compile("foo") if err != nil { // ... 处理错误 } result, err := precompiled.Search(data) // result = "bar"

Compile返回*JMESPath对象,之后反复调用该对象的Search(data)方法即可。从 vendor/github.com/jmespath/go-jmespath/api.go 的实现可见,Compile仅做一次解析并缓存 AST 与解释器(JMESPath结构体持有ast ASTNode与intr *treeInterpreter两个字段)。

该结构体的文档注释明确写着:"A JMESPath is safe for concurrent use by multiple goroutines"(编译后的 JMESPath 可被多个 goroutine 并发安全地使用)。这意味着你可以把编译结果保存在全局变量中,供 HTTP 服务的高并发请求共享。

对于表达式在编译期就必须合法的场景,MustCompile(api.go)提供了便捷封装:解析失败时直接panic,适合在init()或包级变量初始化处使用,让错误在程序启动时即暴露。其 panic 消息会携带完整表达式与错误详情:

var jp = jmespath.MustCompile("foo.bar[*].baz")

五、源码级剖析:从词法到解释的三阶段管线

go-jmespath 的内部结构是典型的"词法分析 → 语法分析 → 解释执行"三段式管线,仓库中三个文件分别对应一个阶段:

  1. 词法分析器(vendor/github.com/jmespath/go-jmespath/lexer.go):Lexer结构体逐字符扫描表达式,输出 token 流。token 类型定义在文件开头的tokType常量中,包括tStar(*)、tDot(.)、tFilter(?)、tFlatten([])、tLbracket/tRbracket、tNumber、tString、tExpref(&)等。同时该文件定义了贯穿全局的错误类型SyntaxError(见下文第六节)。

  2. 语法分析器(vendor/github.com/jmespath/go-jmespath/parser.go):Parser.Parse(L125)采用Pratt 解析器(自顶向下运算符优先级解析)构建抽象语法树。核心方法是parseExpression(bindingPower int)(L145),依据绑定权值驱动递归下降;led(L220)处理中缀运算符如.、[*]、[?]、|,nud(L317)处理前缀/字面量;parseMultiSelectList(L416)与parseMultiSelectHash(L442)分别解析[a, b]和{a: b}多选语法。ASTNode还实现了PrettyPrint方法,可用于调试时输出 AST 结构。

  3. 解释器(vendor/github.com/jmespath/go-jmespath/interpreter.go):treeInterpreter.Execute(node, value)(L31)递归遍历 AST 并求值。值得注意的是,其求值大量依赖 Go 反射:fieldFromStruct(L317)、flattenWithReflection(L342)、sliceWithReflection(L363)、filterProjectionWithReflection(L381)、projectWithReflection(L404)等函数都通过反射机制操作任意 Go 结构,这正是Search能接收任意interface{}数据的原因——既可以是map[string]interface{},也可以是任意 Go 结构体指针。

六、内置函数全集与参数约束

JMESPath 表达式除语法操作符外,还支持函数调用。go-jmespath 的完整函数表定义在 vendor/github.com/jmespath/go-jmespath/functions.go 的newFunctionCaller()中(L125 起),每个函数通过functionEntry结构声明名称、参数类型规格(argSpec)、处理器实现与是否含表达式引用(hasExpRef)。

函数参数类型规格功能
lengthstring / array / object返回字符串长度、数组元素数或对象键数
starts_withstring, string判断字符串是否以指定前缀开头
ends_withstring, string判断字符串是否以指定后缀结尾
containsarray / string, any判断数组是否含某元素或字符串是否含某子串
abs/ceil/floornumber绝对值 / 向上取整 / 向下取整
avg/sumarray[number]数值数组平均值 / 求和
min/maxarray[number] / array[string]求最小 / 最大元素
min_by/max_byarray, expref按表达式引用求值后的最小 / 最大元素
sortarray[string] / array[number]原地排序
sort_byarray, expref按表达式引用排序
joinstring, array[string]以分隔符拼接字符串数组
reversearray / string反转数组或字符串
keys/valuesobject返回对象键数组 / 值数组
typeany返回值的 JMESPath 类型名
mergeobject, variadic合并多个对象(后者覆盖前者)
to_array/to_string/to_numberany类型转换
not_nullany, variadic返回第一个非 null 实参
mapexpref, array对数组每个元素应用表达式引用

两个值得注意的实现细节:

  • 类型系统:文件头部定义了jpType常量(number、string、array、object、array[number]、array[string]、expref、any),resolveArgs(L331 附近)在调用处理器前会对实参做类型校验与强制转换,不匹配时报错——这就是函数参数具有强类型约束的底层机制。
  • 表达式引用(expref):min_by、max_by、sort_by、map声明了hasExpRef: true,其参数类型为jpExpref,配合&前缀语法(如foo[*] \| sort_by(&@, &age))实现"按表达式求值后再比较/变换"的高级能力。
  • 有趣的命名细节:map函数在函数表中的name字段实际注册为"amp"(推测为&操作符字面量的映射名),但其暴露给使用者的语法仍是标准map(&expr, array)。

排序类函数(sort_by、min_by、max_by)的底层比较器byExprString与byExprFloat(functions.go L43-L119)会在每次比较时对左右元素分别执行表达式引用,并通过hasError标记处理求值失败的情况。

七、错误处理:SyntaxError与错误定位

表达式写错时,解析阶段会返回SyntaxError,其定义位于 vendor/github.com/jmespath/go-jmespath/lexer.go:

type SyntaxError struct { msg string // 展示给用户的错误信息 Expression string // 触发错误的表达式 Offset int // 错误在字符串中的位置 }

该错误类型携带了完整的表达式文本与出错偏移量Offset,并提供了HighlightLocation()方法(L47-L49),它会在表达式下方、错误位置处放置一个^字符,帮助开发者快速定位语法问题:

func (e SyntaxError) HighlightLocation() string { return e.Expression + "\n" + strings.Repeat(" ", e.Offset) + "^" }

例如对非法表达式调用该方法,输出形如:

foo.bar[ ^

在使用Compile或Search时,应始终检查返回的error,并可在日志中输出HighlightLocation()的结果,让排查成本降到最低。

八、在 Agones 仓库中的依赖关系与引入路径

go-jmespath 在本仓库中并未被业务代码直接调用,但它的引入路径本身颇具代表性,值得记录:

  • go.mod 第 66 行声明github.com/aws/aws-sdk-go v1.44.176 // indirect,第 99 行声明github.com/jmespath/go-jmespath v0.4.0 // indirect;
  • vendor/modules.txt 中将github.com/jmespath/go-jmespath v0.4.0标记为explicit; go 1.14,并给出包路径github.com/jmespath/go-jmespath;
  • AWS SDK 中确实存在真实调用:在 vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go 中,SDK 通过jmespath.Search(path, i)对结构体按路径字符串取值——这是 go-jmespath 在生态中最常见的"被嵌入"方式:作为通用 JSON 路径求值引擎,被高层 SDK 与配置系统作为依赖引入。

如果读者在自己的 Go 项目(包括基于本仓库改造的服务)中需要直接使用该能力,只需保证 go.mod 中存在该依赖,然后像本文第二节那样import "github.com/jmespath/go-jmespath"即可,无需在 vendor 中做额外配置(仓库 vendor 目录已包含其完整源码与测试依赖)。

九、典型应用场景与延伸学习

综合 README 与源码实现,go-jmespath 适合以下场景:

  1. 配置驱动的数据提取:用表达式替代手写的多层type assertion,让数据访问逻辑外部化、可配置化;
  2. API 响应裁剪与聚合:从大型嵌套 JSON(如云服务返回的复杂对象)中快速抽取需要的字段子集;
  3. 批量数据处理:结合Compile预编译与 goroutine 并发安全特性,在高吞吐路径上复用同一查询;
  4. 测试断言:在测试中用它精准定位嵌套字段,替代冗长的遍历代码。

关于 JMESPath 语言的更多内容,README 建议读者深入学习官方提供的三份资料:JMESPath 语言教程(Tutorial)、各语言实现库列表(Libraries)、以及完整的语言规范(Specification)。go-jmespath 遵循该公开规范实现,本文介绍的全部语法与函数均以该规范为准绳。读者也可以查看仓库中 go-jmespath 自带的 Makefile 了解其测试与基准流程。

十、小结

go-jmespath 用一个函数、两个结构体、三阶段管线,为 Go 开发者提供了与语言无关的 JSON 查询能力。核心 API 即Search与Compile/MustCompile:前者适合一次性查询,后者适合高频复用且并发安全的场景。其内置的投影、过滤、管道与二十余个类型安全的内置函数,足以覆盖绝大多数 JSON 变换需求。在 Agones 仓库中,它作为 AWS SDK 的间接依赖随 vendor 一并管理,为任何依赖云 SDK 的服务提供了开箱即用的 JSON 路径求值能力——理解它的用法,等于为你的 Go 工具箱中再添一件处理嵌套数据的利器。

  • 游戏开发
  • 云原生

【免费下载链接】agones

Dedicated Game Server Hosting and Scaling for Multiplayer Games on Kubernetes

项目地址:https://gitcode.com/gh_mirrors/ag/agones
点击查看免费下载
上一篇:5个实用技巧:怎样高效使用开源AI图像放大工具Upscayl
下一篇:如何解决Jellyfin元数据刮削难题?MetaTube插件的全面优化指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 2:00:02

可靠性密码 | 高可靠性之光学设计与制程管控(上)

△ 高可靠性固体激光器激光技术飞速发展的当下,固体激光器凭借其高功率、高效率、长寿命等优势,在工业加工、医疗美容等领域占据重要地位。然而,随着应用场景的日益复杂和严苛,对激光器的可靠性要求也愈发严格。光学系统作为激光器…

作者头像 李华
网站建设 2026/10/10 1:59:37

海康iSecure Center生产级部署:从环境校准到服务验证

简介:本资源是一份面向安防系统集成工程师、IT运维人员及弱电项目实施人员的海康威视iSecure Center综合安防平台(含视频监控、门禁管理、报警管理)全流程部署实操指南,聚焦生产环境落地难点,解决从零搭建平台时的环境…

作者头像 李华
网站建设 2026/10/10 1:57:51

SMP/NUMA/PER_CPU

whywhathow PER_CPU 从上图中我们可以看到,各种源文件中 静态percpu变量 通过DEFINE_PER_CPU的方式,定义了很多percpu变量,这些变量根据vmlinux.lds.S中的相关定义,会被linker聚合在一起,然后放到最终vmlinux文件的&…

作者头像 李华
网站建设 2026/10/10 1:56:40

休闲食品定制加工厂避坑挑选指南:福建实力参考

休闲食品定制加工厂怎么挑选?很多经销商、餐饮茶饮品牌、酒店和贸易商在采购时都会遇到这个难题。下面围绕三个高频问题,逐一说明挑选思路,并结合福建龙海一家深耕30余年的休闲食品定制厂家旭源食品的情况,提供实际参考。 Q1:挑选…

作者头像 李华