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. 性能优化技巧 ===
使用预解析后的对象获取名字: 小明

💡 几个值得注意的小细节

  1. ForEach 遍历:在代码的条件过滤部分,我使用了 .ForEach() 方法。它的回调函数需要返回一个 bool 值,如果你在处理过程中找到了想要的结果想提前退出循环,直接 return false 即可。
  2. 特殊字符转义:注意看 metadata.fav\.movie,因为真实的键名里带有一个点(.),而 gjson 默认用点来代表层级,所以必须加反斜杠 \ 告诉它“这个点是键名的一部分”。
  3. 链式调用:gjson 的 Result 对象支持链式调用,比如在遍历朋友时,可以直接在 value 上继续 .Get("name"),非常丝滑。
posted @ 2026-05-15 19:01  干炸小黄鱼  阅读(29)  评论(0)    收藏  举报