golang每日一库--json解析库gjson
gjson 是 Go 语言中一款非常轻量且高性能的 JSON 解析库,被很多开发者称为处理 JSON 的“瑞士军刀”。它最大的特点是无需定义结构体,也不用进行繁琐的类型断言,仅用一行代码就能提取任意深度嵌套的 JSON 值。
下面为你详细介绍它的安装和核心用法:
🚀 安装
在你的项目目录下执行以下命令即可安装:
go get -u github.com/tidwall/gjson
💡 核心用法:点语法与路径查询
gjson 的核心在于其强大的路径语法。你只需要传入 JSON 字符串和对应的键路径(使用 . 分隔),就能快速获取数据。
假设我们有如下的 JSON 数据:
const json = `{
"user": {
"name": "小明",
"age": 18
},
"tags": ["Go", "Rust", "Python"],
"friends": [
{"name": "张三", "age": 20},
{"name": "李四", "age": 26}
]
}`
1. 基础取值与嵌套访问
直接使用 gjson.Get 配合点号 . 即可获取深层嵌套的值;访问数组元素时,直接使用索引(从0开始)。
import (
"fmt"
"github.com/tidwall/gjson"
)
func main() {
name := gjson.Get(json, "user.name").String() // 获取嵌套字段: "小明"
age := gjson.Get(json, "user.age").Int() // 获取数字: 18
firstTag := gjson.Get(json, "tags.0").String() // 获取数组第一个元素: "Go"
fmt.Println(name, age, firstTag)
}
2. 数组遍历与批量提取
使用 # 符号可以获取数组长度,或者使用 .# 来批量提取数组中所有对象的某个字段。
// 获取 tags 数组的长度
tagCount := gjson.Get(json, "tags.#").Int() // 结果: 3
// 批量提取 friends 数组中所有人的名字
names := gjson.Get(json, "friends.#.name").Array()
for _, n := range names {
fmt.Println(n.String()) // 依次输出: 张三, 李四
}
3. 条件过滤查询
gjson 支持类似 SQL 的条件查询,语法为 #(条件),可以精准筛选出符合要求的元素。
// 查找 friends 数组中第一个年龄大于 25 岁的人的名字
result := gjson.Get(json, `friends.#(age>25).name`)
if result.Exists() {
fmt.Println("找到啦:", result.String()) // 输出: 李四
}
4. 常用类型转换与校验
gjson.Get 返回的是一个 gjson.Result 对象,你可以根据需要将其转换为对应的 Go 原生类型。
- 类型转换方法:
.String()、.Int()、.Float()、.Bool()、.Array()(转为[]gjson.Result切片)、.Map()(转为map[string]gjson.Result)。 - 存在性判断:在处理可能不存在的字段时,强烈建议使用
.Exists()方法进行判断,这比判断返回值是否为空字符串更可靠。
// 推荐的健壮写法
if val := gjson.Get(json, "user.email"); val.Exists() {
fmt.Println(val.String())
} else {
fmt.Println("该字段不存在")
}
⚡ 进阶与避坑指南
- 性能优化:gjson 的性能极高,基准测试显示单次
Get操作仅需约 200ns,且实现了零内存分配,非常适合高并发场景。如果你需要对同一个 JSON 字符串进行多次查询,可以先使用parsed := gjson.Parse(json)预解析,再通过parsed.Get("path")提取,效率会更高。 - 处理字节流:如果你的 JSON 数据是
[]byte类型(例如从 HTTP 请求的 Body 中直接读取),请使用gjson.GetBytes(data, "path"),这样可以避免不必要的string类型转换开销。 - JSON 校验:在解析外部传入的不可信 JSON 之前,可以先用
gjson.Valid(json)检查其格式是否合法。 - 修改 JSON:gjson 专注于“读取”。如果你需要动态修改或设置 JSON 的值,可以搭配同作者开发的
sjson库一起使用。
一个完整的 Go 示例代码。
这段代码模拟了我们在实际开发中经常遇到的场景:处理一个包含用户信息、标签、朋友列表的复杂嵌套 JSON。它把前面提到的基础取值、数组操作、条件过滤、类型安全判断以及性能优化等核心用法全部串联了起来。
直接复制以下代码到本地环境运行:
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
func main() {
// 模拟一份复杂的嵌套 JSON 数据
jsonData := `{
"user": {
"name": "小明",
"age": 18,
"is_active": true
},
"tags": ["Go", "Rust", "Python"],
"friends": [
{"name": "张三", "age": 20, "city": "北京"},
{"name": "李四", "age": 26, "city": "上海"},
{"name": "王五", "age": 30, "city": "深圳"}
],
"metadata": {
"fav.movie": "星际穿越"
}
}`
fmt.Println("=== 1. 基础取值与嵌套访问 ===")
name := gjson.Get(jsonData, "user.name").String()
age := gjson.Get(jsonData, "user.age").Int()
isActive := gjson.Get(jsonData, "user.is_active").Bool()
// 键名中包含特殊字符(如点号),需要使用 \ 进行转义
favMovie := gjson.Get(jsonData, `metadata.fav\.movie`).String()
fmt.Printf("用户名: %s, 年龄: %d, 是否活跃: %v, 喜欢的电影: %s\n\n", name, age, isActive, favMovie)[[source_group_web_1]]
fmt.Println("=== 2. 数组遍历与批量提取 ===")
// 获取数组长度
tagCount := gjson.Get(jsonData, "tags.#").Int()
fmt.Printf("用户拥有的标签数量: %d\n", tagCount)
// 批量提取 friends 数组中所有人的名字
fmt.Print("所有朋友的名字: ")
names := gjson.Get(jsonData, "friends.#.name").Array()
for _, n := range names {
fmt.Printf("%s ", n.String())
}
fmt.Println("\n")
fmt.Println("=== 3. 条件过滤查询 ===")
// 查找 friends 数组中第一个年龄大于 25 岁的人的名字
result := gjson.Get(jsonData, `friends.#(age>25).name`)
if result.Exists() {
fmt.Printf("找到第一位年龄超过25岁的朋友: %s\n", result.String())
}
// 找出所有城市在上海或北京的朋友对象
fmt.Println("筛选出住在一线城市(北京/上海)的朋友:")
gjson.Get(jsonData, `friends.#(city%"*京|*海")#`).ForEach(func(_, value gjson.Result) bool {
friendName := value.Get("name").String()
friendCity := value.Get("city").String()
fmt.Printf("- %s 在 %s\n", friendName, friendCity)
return true // 返回 true 继续遍历,返回 false 则停止
})
fmt.Println()[[source_group_web_2]]
fmt.Println("=== 4. 存在性判断与健壮写法 ===")
// 尝试获取一个不存在的字段
emailResult := gjson.Get(jsonData, "user.email")
if emailResult.Exists() {
fmt.Println("用户的邮箱是:", emailResult.String())
} else {
fmt.Println("警告: user.email 字段不存在!")
}
fmt.Println()
fmt.Println("=== 5. 性能优化技巧 ===")
// 如果需要对同一个 JSON 字符串进行多次高频查询,建议先预解析
parsedJSON := gjson.Parse(jsonData)
fmt.Println("使用预解析后的对象获取名字:", parsedJSON.Get("user.name").String())
}
📌 代码运行预期结果
当你执行这段代码时,终端会打印出如下内容:
=== 1. 基础取值与嵌套访问 ===
用户名: 小明, 年龄: 18, 是否活跃: true, 喜欢的电影: 星际穿越
=== 2. 数组遍历与批量提取 ===
用户拥有的标签数量: 3
所有朋友的名字: 张三 李四 王五
=== 3. 条件过滤查询 ===
找到第一位年龄超过25岁的朋友: 李四
筛选出住在一线城市(北京/上海)的朋友:
- 张三 在 北京
- 李四 在 上海
=== 4. 存在性判断与健壮写法 ===
警告: user.email 字段不存在!
=== 5. 性能优化技巧 ===
使用预解析后的对象获取名字: 小明
💡 几个值得注意的小细节
- ForEach 遍历:在代码的条件过滤部分,我使用了
.ForEach()方法。它的回调函数需要返回一个bool值,如果你在处理过程中找到了想要的结果想提前退出循环,直接return false即可。 - 特殊字符转义:注意看
metadata.fav\.movie,因为真实的键名里带有一个点(.),而 gjson 默认用点来代表层级,所以必须加反斜杠\告诉它“这个点是键名的一部分”。 - 链式调用:gjson 的
Result对象支持链式调用,比如在遍历朋友时,可以直接在value上继续.Get("name"),非常丝滑。

浙公网安备 33010602011771号