惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Vercel News
Vercel News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
C
Check Point Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
The Hacker News
The Hacker News
L
LINUX DO - 热门话题
T
Tenable Blog
Hugging Face - Blog
Hugging Face - Blog
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
P
Proofpoint News Feed
H
Hacker News: Front Page
P
Privacy & Cybersecurity Law Blog
月光博客
月光博客
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
博客园_首页
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
CERT Recently Published Vulnerability Notes
Forbes - Security
Forbes - Security
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
博客园 - 叶小钗
T
Threat Research - Cisco Blogs
aimingoo的专栏
aimingoo的专栏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hacker News - Newest:
Hacker News - Newest: "LLM"
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
O
OpenAI News
G
Google Developers Blog
Martin Fowler
Martin Fowler
罗磊的独立博客
S
SegmentFault 最新的问题
T
Tor Project blog
量子位

博客园 - 干炸小黄鱼

timex 处理时间戳 gorm-gen go 雪花算法 golang每日一库--协程池库ants python高级编程-asyncio python高级编程-condition python高级编程-event python装饰器-自动重试 EAP系统 go实现实现 SECS/GEM 协议 设备通信协议 SECS go项目使用Jenkins进行CICD go操作ES mongo db聚合查询 go如何使用mongodb Apache ShardingSphere paxos and raft (分布式一致性算法) go使用zookeeper分布式锁以及和redis差异 go使用 seata 示例 Alibaba 分布式事务 Seata go中使用saga go中使用TCC示例 分布式事务TCC 熔断器 Hystrix OR Sentinel k8s下部署consul and etcd Consul OR Etcd 【力扣hot100】双指针-盛水最多的容器 【力扣hot100】滑动窗口-最小覆盖子串 shell脚本合集 分布式id生成器 springboot通用CURD Python PB级检索系统架构设计 rancher 在三台机器搭建k8s集群 python ssh clinet 数据库排序Null值字段靠后/靠前 常规web项目 docker-compose 例子 手搓一个验证码 使用itertools 中的groupby 对字典数组进行分组后排序 使用开源库 geoip2 获取某ip的经纬度地理信息 python中 apscheduler.schedulers.blocking.BlockingScheduler 定时执行任务 简单的python web项目的docker-compose.yml 示例 python和sliver交互 golang sliver二次开发自定义命令(格式乱后面再调) pydantic做参数校验 基于rancher部署k8s 地理位置相关基础数据 flask migrate时报错 Can't locate revision identified by '3d80e4c025df'
golang每日一库--json解析库gjson
干炸小黄鱼 · 2026-05-15 · via 博客园 - 干炸小黄鱼

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"),非常丝滑。