介绍
在 Golang
中,解析 JSON
数据是一项非常常见的任务。Go
提供了标准的JSON
包,可以轻松地将JSON
数据序列化和反序列化。但是,在使用标准JSON
包解析大型复杂JSON
结构时,可能存在些许不足,例如代码冗余,性能瓶颈等问题。针对这些问题,目前有许多优秀的JSON
解析框架,GJSON
是其中一个很不错的选择。本文将详细讲解如何使用GJSON
框架解析JSON
数据。
安装
GJSON
模块可以通过go get
命令来安装。
go get github.com/tidwall/gjson
gjson实际上是get + json的缩写,用于读取 JSON 串,同样的还有一个sjson(set + json)库用来设置 JSON 串。
基本使用
package main
import (
"fmt"
"github.com/tidwall/gjson"
)
func main() {
json := `{"name":{"first":"li","last":"dj"},"age":18}`
lastName := gjson.Get(json, "name.last")
fmt.Println("last name:", lastName.String())
age := gjson.Get(json, "age")
fmt.Println("age:", age.Int())
email := gjson.Get(json, "email")
// json 中不存在该字段时,String为空串,Int为0,即返回想要转换的类型的零值
fmt.Println("email:", email.String())
}
使用很简单,只需要传入 JSON
串和要读取的键路径即可。注意一点细节,因为gjson.Get()
函数实际上返回的是gjson.Result
类型,我们要调用其相应的方法进行转换对应的类型。如上面的String()
和Int()
方法。
如果是直接打印输出,其实可以省略String()
,fmt
包的大部分函数都可以对实现fmt.Stringer
接口的类型调用String()
方法。
注意:Int()返回的值是int64类型
方法 | 描述 |
---|---|
Bool() | 尝试将此值转换为布尔型。 |
Float64() | 尝试将此值转换为float64。 |
Int() | 尝试将此值转换为int64。 |
Uint64() | 尝试将此值转换为uint64。 |
String() | 获取字符串版本(或数字,布尔值等) |
键路径
键路径实际上是以.
分隔的一系列键。gjson
支持在键中包含通配符*
和?
,*
匹配任意多个字符,?
匹配单个字符,例如ca*
可以匹配cat、cate、cake
等以ca
开头的键,ca?
只能匹配cat、cap
等以ca
开头且后面只有一个字符的键。
数组使用键名 + . + 索引
(索引从 0
开始)的方式读取元素,如果键pets
对应的值是一个数组,那么pets.0
读取数组的第一个元素,pets.1
读取第二个元素。
数组长度使用键名 + . + #
获取,例如pets.#
返回数组pets
的长度。
如果键名中出现.
,那么需要使用\
进行转义。
package main
const json = `
{
"name":{"first":"Tom", "last": "Anderson"},
"age": 37,
"children": ["Sara", "Alex", "Jack"],
"fav.movie": "Dear Hunter",
"friends": [
{"first": "Dale", "last":"Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
]
}
`
func main() {
fmt.Println("last name:", gjson.Get(json, "name.last"))
fmt.Println("age:", gjson.Get(json, "age"))
fmt.Println("children:", gjson.Get(json, "children"))
fmt.Println("children count:", gjson.Get(json, "children.#"))
fmt.Println("second child:", gjson.Get(json, "children.1"))
fmt.Println("third child*:", gjson.Get(json, "child*.2"))
fmt.Println("first c?ild:", gjson.Get(json, "c?ildren.0"))
fmt.Println("fav.moive", gjson.Get(json, `fav.\moive`))
fmt.Println("first name of friends:", gjson.Get(json, "friends.#.first"))
fmt.Println("last name of second friend:", gjson.Get(json, "friends.1.last"))
}
前 3 个比较简单,就不赘述了。看后面几个:
children.#
:返回数组children
的长度;children.1
:读取数组children
的第2
个元素(注意索引从0
开始);child*.2
:首先child*
匹配children
,.2
读取第3
个元素;c?ildren.0
:c?ildren
匹配到children
,.0
读取第一个元素;fav.\moive
:因为键名中含有.
,故需要\
转义;friends.#.first
:如果数组后#
后还有内容,则以后面的路径读取数组中的每个元素,返回一个新的数组。所以该查询返回的数组是由friends
每个元素的first
字段组成;friends.1.last
:读取friends
第2
个元素的last
字段。
运行结果:
last name: Anderson
age: 37
children: ["Sara", "Alex", "Jack"]
children count: 3
second child: Alex
third child*: Jack
first c?ild: Sara
fave.moive
first name of friends: ["Dale","Roger","Jane"]
last name of second friend: Craig
对于数组,gjson
还支持按条件查询元素,#(条件)
返回第一个满足条件的元素,#(条件)#
返回所有满足条件的元素。括号内的条件可以有==、!=、<、<=、>、>=
,还有简单的模式匹配%
(符合某个模式),!%
(不符合某个模式):
fmt.Println(gjson.Get(json, `friends.#(last="Murphy").first`))
fmt.Println(gjson.Get(json, `friends.#(last="Murphy")#.first`))
fmt.Println(gjson.Get(json, "friends.#(age>45)#.last"))
fmt.Println(gjson.Get(json, `friends.#(first%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(first!%"D*").last`))
fmt.Println(gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`))
还是使用上面的 JSON 串。
friends.#(last="Murphy").first
:friends.#(last="Murphy")
返回数组friends
中第一个last
为Murphy
的元素,.first
表示取出该元素的first
字段返回;friends.#(last="Murphy")#.first
:friends.#(last="Murphy")#
返回数组friends
中所有的last
为Murphy
的元素,然后读取它们的first
字段放在一个数组中返回。注意与上面一个的区别;friends.#(age>45)#.last
:friends.#(age>45)#
返回数组friends
中所有年龄大于45
的元素,然后读取它们的last
字段返回;friends.#(first%"D*").last
:friends.#(first%"D*")
返回数组friends
中第一个first
字段满足模式D*
的元素,取出其last
字段返回;friends.#(first!%"D*").last
:friends.#(first!%“D*”)
返回数组friends
中第一个first
字段不满足模式D*
的元素,读取其last
字段返回;friends.#(nets.#(=="fb"))#.first
:这是个嵌套条件,friends.#(nets.#(=="fb"))#
返回数组friends
的元素的nets
字段中有fb
的所有元素,然后取出first
字段返回。
运行结果:
Dale
["Dale","Jane"]
["Craig","Murphy"]
Murphy
Craig
["Dale","Roger"]
校验 JSON
调用gjson.Get()
时,gjson
假设我们传入的 JSON
串是合法的。如果 JSON
非法也不会panic
,这时会返回不确定的结果:
func main() {
const json = `{"name":dj,age:18}`
fmt.Println(gjson.Get(json, "name"))
}
上面 JSON
串是非法的,dj
和age
都没有加上双引号(实际上习惯了 Go
语言map
的写法,很容易把 JSON
写成这样😭)。上面代码输出18
,显然是错误的。我们可以使用gjson.Valid()
检测 JSON
串是否合法:
if !gjson.Valid(json) {
fmt.Println("error")
} else {
fmt.Println("ok")
}
一次获取多个值
调用gjson.Get()
一次只能读取一个值,多次调用又比较麻烦,gjson
提供了GetMany()
可以一次读取多个值,返回一个数组[]gjson.Result
const json = `
{
"name":"dj",
"age":18,
"pets": ["cat", "dog"],
"contact": {
"phone": "123456789",
"email": "dj@example.com"
}
}`
func main() {
results := gjson.GetMany(json, "name", "age", "pets.#", "contact.phone")
for _, result := range results {
fmt.Println(result)
}
}
上面代码返回字段name
、age
、数组pets
的长度和contact.phone
字段。