快速获取JSON值
GJSON是一个Go语言包,提供了一种快速简单的方法来获取JSON文档中的值。它具有诸如一行检索、点号路径语法、迭代和解析JSON行等特性。
另外还可以查看用于修改JSON的SJSON,以及命令行工具JJ。
本README是GJSON使用的快速概述,更多信息请查看GJSON语法。
入门
安装
要开始使用GJSON,请安装Go并运行go get
:
$ go get -u github.com/tidwall/gjson
这将检索该库。
获取值
Get函数在JSON中搜索指定路径。路径使用点号语法,例如"name.last"或"age"。找到值后立即返回。
package main
import "github.com/tidwall/gjson"
const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
func main() {
value := gjson.Get(json, "name.last")
println(value.String())
}
这将打印:
Prichard
还有GetMany函数可以一次获取多个值,以及GetBytes用于处理JSON字节切片。
路径语法
以下是路径语法的快速概述,更完整的信息请查看GJSON语法。
路径是由点分隔的一系列键。 键可以包含特殊通配符字符'*'和'?'。 要访问数组值,请使用索引作为键。 要获取数组中的元素数量或访问子路径,请使用'#'字符。 点和通配符字符可以用''转义。
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer 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"]}
]
}
"name.last" >> "Anderson"
"age" >> 37
"children" >> ["Sara","Alex","Jack"]
"children.#" >> 3
"children.1" >> "Alex"
"child*.2" >> "Jack"
"c?ildren.0" >> "Sara"
"fav\.movie" >> "Deer Hunter"
"friends.#.first" >> ["Dale","Roger","Jane"]
"friends.1.last" >> "Craig"
你也可以使用#(...)
查询数组以找到第一个匹配项,或使用#(...)#
找到所有匹配项。查询支持==
、!=
、<
、<=
、>
、>=
比较运算符以及简单的模式匹配%
(类似)和!%
(不类似)运算符。
friends.#(last=="Murphy").first >> "Dale"
friends.#(last=="Murphy")#.first >> ["Dale","Jane"]
friends.#(age>45)#.last >> ["Craig","Murphy"]
friends.#(first%"D*").last >> "Murphy"
friends.#(first!%"D*").last >> "Craig"
friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"]
请注意,在v1.3.0之前,查询使用#[...]
括号。这在v1.3.0中被更改,以避免与新的多路径语法混淆。为了向后兼容,#[...]
将继续工作,直到下一个主要版本。
结果类型
GJSON支持JSON类型string
、number
、bool
和null
。
数组和对象以原始JSON类型返回。
Result
类型包含以下之一:
bool,用于JSON布尔值
float64,用于JSON数字
string,用于JSON字符串字面量
nil,用于JSON null
要直接访问值:
result.Type // 可以是String、Number、True、False、Null或JSON
result.Str // 存储字符串
result.Num // 存储float64数字
result.Raw // 存储原始json
result.Index // 原始值在原始json中的索引,零表示索引未知
result.Indexes // 匹配包含'#'查询字符的路径上所有元素的索引
有多种适用于结果的便捷函数:
result.Exists() bool
result.Value() interface{}
result.Int() int64
result.Uint() uint64
result.Float() float64
result.String() string
result.Bool() bool
result.Time() time.Time
result.Array() []gjson.Result
result.Map() map[string]gjson.Result
result.Get(path string) Result
result.ForEach(iterator func(key, value Result) bool)
result.Less(token Result, caseSensitive bool) bool
result.Value()
函数返回一个 interface{}
,需要类型断言,是以下Go类型之一:
boolean >> bool
number >> float64
string >> string
null >> nil
array >> []interface{}
object >> map[string]interface{}
result.Array()
函数返回一个值数组。如果结果表示不存在的值,则返回空数组。如果结果不是JSON数组,返回值将是包含一个结果的数组。
64位整数
result.Int()
和 result.Uint()
调用能够读取全部64位,允许处理大型JSON整数。
result.Int() int64 // -9223372036854775808 到 9223372036854775807
result.Uint() uint64 // 0 到 18446744073709551615
修饰符和路径链接
1.2版本新增了对修饰符函数和路径链接的支持。
修饰符是对json执行自定义处理的路径组件。
多个路径可以使用管道字符"链接"在一起。这对于从修改后的查询中获取结果很有用。
例如,在上述json文档上使用内置的 @reverse
修饰符,我们将获取 children
数组并反转顺序:
"children|@reverse" >> ["Jack","Alex","Sara"]
"children|@reverse|0" >> "Jack"
目前有以下内置修饰符:
@reverse
: 反转数组或对象的成员。@ugly
: 从json文档中删除所有空白。@pretty
: 使json文档更易读。@this
: 返回当前元素。可用于检索根元素。@valid
: 确保json文档有效。@flatten
: 扁平化数组。@join
: 将多个对象合并为单个对象。@keys
: 返回对象的键数组。@values
: 返回对象的值数组。@tostr
: 将json转换为字符串。包装json字符串。@fromstr
: 将字符串从json转换。解包json字符串。@group
: 对对象数组进行分组。参见 e4fc67c。@dig
: 无需提供完整路径即可搜索值。参见 e8e87f2。
修饰符参数
修饰符可以接受可选参数。参数可以是有效的JSON文档或仅字符。
例如,@pretty
修饰符将json对象作为其参数。
@pretty:{"sortKeys":true}
这会使json更美观并对其所有键进行排序。
{
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"age": 44, "first": "Dale", "last": "Murphy"},
{"age": 68, "first": "Roger", "last": "Craig"},
{"age": 47, "first": "Jane", "last": "Murphy"}
],
"name": {"first": "Tom", "last": "Anderson"}
}
@pretty
的完整选项列表包括 sortKeys
、indent
、prefix
和 width
。
更多信息请参见 Pretty Options。
自定义修饰符
您也可以添加自定义修饰符。
例如,这里我们创建一个修饰符,将整个json文档转换为大写或小写。
gjson.AddModifier("case", func(json, arg string) string {
if arg == "upper" {
return strings.ToUpper(json)
}
if arg == "lower" {
return strings.ToLower(json)
}
return json
})
"children|@case:upper" >> ["SARA","ALEX","JACK"]
"children|@case:lower|@reverse" >> ["jack","alex","sara"]
JSON Lines
使用 ..
前缀支持 JSON Lines,将多行文档视为数组。
例如:
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#(name="May").age >> 57
ForEachLines
函数将遍历JSON行。
gjson.ForEachLine(json, func(line gjson.Result) bool{
println(line.String())
return true
})
获取嵌套数组值
假设你想从以下json中获取所有的姓氏:
{
"programmers": [
{
"firstName": "Janet",
"lastName": "McLaughlin",
}, {
"firstName": "Elliotte",
"lastName": "Hunter",
}, {
"firstName": "Jason",
"lastName": "Harold",
}
]
}
你可以像这样使用路径 "programmers.#.lastName":
result := gjson.Get(json, "programmers.#.lastName")
for _, name := range result.Array() {
println(name.String())
}
你也可以查询数组中的对象:
name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
println(name.String()) // 打印 "Elliotte"
遍历对象或数组
ForEach
函数允许快速遍历对象或数组。
对于对象,键和值会被传递给迭代器函数。
对于数组,只传递值。
从迭代器返回 false
将停止迭代。
result := gjson.Get(json, "programmers")
result.ForEach(func(key, value gjson.Result) bool {
println(value.String())
return true // 继续迭代
})
简单解析和获取
有一个 Parse(json)
函数可以进行简单解析,以及 result.Get(path)
可以搜索结果。
例如,以下所有操作都会返回相同的结果:
gjson.Parse(json).Get("name").Get("last")
gjson.Get(json, "name").Get("last")
gjson.Get(json, "name.last")
检查值是否存在
有时你只想知道某个值是否存在。
value := gjson.Get(json, "name.last")
if !value.Exists() {
println("没有姓氏")
} else {
println(value.String())
}
// 或者一步完成
if gjson.Get(json, "name.last").Exists() {
println("有姓氏")
}
验证 JSON
Get*
和 Parse*
函数假定 json 格式良好。格式错误的 json 不会引发 panic,但可能返回意外结果。
如果你正在处理来自不可预测来源的 JSON,那么你可能想在使用 GJSON 之前进行验证。
if !gjson.Valid(json) {
return errors.New("无效的 json")
}
value := gjson.Get(json, "name.last")
解析到 map
要解析到 map[string]interface{}
:
m, ok := gjson.Parse(json).Value().(map[string]interface{})
if !ok {
// 不是 map
}
处理字节
如果你的 JSON 包含在 []byte
切片中,可以使用 GetBytes 函数。这比 Get(string(data), path)
更可取。
var json []byte = ...
result := gjson.GetBytes(json, path)
如果你正在使用 gjson.GetBytes(json, path)
函数,并且想要避免将 result.Raw
转换为 []byte
,那么你可以使用这种模式:
var json []byte = ...
result := gjson.GetBytes(json, path)
var raw []byte
if result.Index > 0 {
raw = json[result.Index:result.Index+len(result.Raw)]
} else {
raw = []byte(result.Raw)
}
这是一种尽最大努力不分配原始 json 子切片的方法。这种方法利用了 result.Index
字段,它是原始 json 中原始数据的位置。result.Index
的值可能等于零,在这种情况下,result.Raw
会被转换为 []byte
。
性能
GJSON 与 encoding/json、 ffjson、 EasyJSON、 jsonparser 和 json-iterator 的基准测试对比
BenchmarkGJSONGet-16 11644512 311 ns/op 0 B/op 0 allocs/op
BenchmarkGJSONUnmarshalMap-16 1122678 3094 ns/op 1920 B/op 26 allocs/op
BenchmarkJSONUnmarshalMap-16 516681 6810 ns/op 2944 B/op 69 allocs/op
BenchmarkJSONUnmarshalStruct-16 697053 5400 ns/op 928 B/op 13 allocs/op
BenchmarkJSONDecoder-16 330450 10217 ns/op 3845 B/op 160 allocs/op
BenchmarkFFJSONLexer-16 1424979 2585 ns/op 880 B/op 8 allocs/op
BenchmarkEasyJSONLexer-16 3000000 729 ns/op 501 B/op 5 allocs/op
BenchmarkJSONParserGet-16 3000000 366 ns/op 21 B/op 0 allocs/op
BenchmarkJSONIterator-16 3000000 869 ns/op 693 B/op 14 allocs/op
使用的 JSON 文档:
{
"widget": {
"debug": "on",
"window": {
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
},
"image": {
"src": "Images/Sun.png",
"hOffset": 250,
"vOffset": 250,
"alignment": "center"
},
"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"vOffset": 100,
"alignment": "center",
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
}
}
}
每个操作都轮流使用以下搜索路径之一:
widget.window.name
widget.image.hOffset
widget.text.onMouseUp
这些基准测试是在 MacBook Pro 16" 2.4 GHz Intel Core i9 上使用 Go 1.17 运行的,可以在这里找到。