前言
GJSON是一个Go包,它提供了一种快速,简单的方法来从json文档中获取值。它具有诸如单行检索,点符号路径,迭代和解析json行之类的功能。
获得值
获取搜索json以查找指定的路径。路径采用点语法,例如“name.last”或“age”。找到该值后,将立即返回。
1
2
3
4
5
6
7
8
9
10
|
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())
}
|
还有一个GetMany函数可以一次获取多个值,还有GetBytes用于处理JSON字节片。
GJSON路径语法
路径结构
GJSON Path旨在易于表达为一系列由.字符分隔的组件。
除了.还有一些具有特殊含义,包括几个|,#,@,\,*,和?。
举例
给定此JSON
1
2
3
4
5
6
7
8
9
10
11
|
{
"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"]}
]
}
|
以下GJSON路径将评估为随附的值。
基本的
在许多情况下,您只想按对象名称或数组索引检索值。
1
2
3
4
5
6
7
8
|
name.last "Anderson"
name.first "Tom"
age 37
children ["Sara","Alex","Jack"]
children.0 "Sara"
children.1 "Alex"
friends.1 {"first": "Roger", "last": "Craig", "age": 68}
friends.1.first "Roger"
|
通配符
key可能包含特殊的通配符*和?。*匹配任何0个以上的字符字符,?匹配任意一个字符。
1
2
|
child*.2 "Jack"
c?ildren.0 "Sara"
|
转义符
特殊用途的字符,例如.,*和?可以进行转义\。
1
|
fav\.movie "Deer Hunter"
|
数组
#字符允许挖掘JSON数组。
要获得数组的长度,可以单独使用#。
1
2
|
friends.# 3
friends.#.age [44,68,47]
|
查询
您也可以使用#(...)来查询数组中的第一个匹配项,或者使用#(...)#查找所有匹配项。查询支持==,!=,<,<=,>,>=比较运算符,和简单模式匹配%(like)和!%(not like)。
1
2
3
4
5
|
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"
|
要查询数组中的非对象值,可以放弃运算符右侧的字符串。
1
2
|
children.#(!%"*a*") "Alex"
children.#(%"*a*")# ["Sara","Jack"]
|
允许嵌套查询。
1
|
friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"]
|
请注意,在v1.3.0之前,查询使用#[...]方括号。为了避免与新的多路径语法混淆,在v1.3.0中对此进行了更改。为了向后兼容,#[...]它将继续工作直到下一个主要版本。
. vs |
.是标准的分隔符,但它也可以使用|。在大多数情况下,它们最终都返回相同的结果。当为了Arrays 和 Queries而在#之后使用时,|和.表现不同。
这里有些例子
1
2
3
4
5
6
7
8
9
10
11
12
13
|
friends.0.first "Dale"
friends|0.first "Dale"
friends.0|first "Dale"
friends|0|first "Dale"
friends|# 3
friends.# 3
friends.#(last="Murphy")# [{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}]
friends.#(last="Murphy")#.first ["Dale","Jane"]
friends.#(last="Murphy")#|first <non-existent>
friends.#(last="Murphy")#.0 []
friends.#(last="Murphy")#|0 {"first": "Dale", "last": "Murphy", "age": 44}
friends.#(last="Murphy")#.# []
friends.#(last="Murphy")#|# 2
|
让我们分析其中的一些。
路径friends.#(last="Murphy")#导致
1
|
[{"first": "Dale", "last": "Murphy", "age": 44},{"first": "Jane", "last": "Murphy", "age": 47}]
|
所述.first后缀将处理first每个阵列元素上路径之前将结果返回。变成
但是|first后缀实际上是在前一个结果之后处理first路径。由于前一个结果是数组,而不是对象,因此无法处理,因为first不存在
后缀|0返回
1
|
{"first": "Dale", "last": "Murphy", "age": 44}
|
因为0是先前结果的第一个索引。
修饰符
修饰符是对JSON执行自定义处理的路径组件。
例如,@reverse在上述JSON有效负载上使用内置修饰符将反转children数组:
1
2
|
children.@reverse ["Jack","Alex","Sara"]
children.@reverse.0 "Jack"
|
当前有三个内置修饰符:
- @reverse:反转数组或对象的成员。
- @ugly:从JSON中删除所有空格。
- @pretty:使JSON更具可读性。
修饰符参数
修饰符可以接受可选参数。参数可以是有效的JSON有效负载,也可以只是字符。
例如,@pretty修饰符将json对象作为其参数。
1
|
@pretty:{"sortKeys":true}
|
这使得json很漂亮,并对其所有key进行排序。
使用此示例:
1
2
3
4
5
|
{"name": {"first":"Tom","last":"Anderson"}, "age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter", "friends": [
{"first": "Janet", "last": "Murphy", "age": 44}
]}
|
将json格式化为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
"name": {
"first": "Tom",
"last": "Anderson"
},
"age": 37,
"children": ["Sara", "Alex", "Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{
"first": "Janet",
"last": "Murphy",
"age": 44
}
]
}
|
@pretty选项的完整列表是sortKeys, indent, prefix和width。
自定义修饰符
您还可以添加自定义修饰符。
例如,在这里我们创建一个修饰符,使整个JSON有效负载变为大写或小写。
1
2
3
4
5
6
7
8
9
10
11
|
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"]
|
多路径
从v1.3.0开始,GJSON添加了将多个路径连接在一起以形成新文档的功能。在{...}或[...]之间包装用逗号分隔的路径将分别导致新的数组或对象。
例如,使用给定的多路径
1
|
{name.first,age,"the_murphys":friends.#(last="Murphy")#.first}
|
在这里,我们为姓氏为“Murphy”的朋友选择了名字,年龄和名字。
您会注意到,可以提供可选key,在本例中为“the_murphys”,以强制将key分配给值。否则,将使用实际字段的名称,在这种情况下为“first”。如果无法确定名称,则使用“ _”。
这导致
1
|
{"first":"Tom","age":37,"the_murphys":["Dale","Jane"]}
|
结果类型
GJSON支持JSON类型string,number,bool,和null。数组和对象作为其原始json类型返回。
该Result类型包含以下之一:
1
2
3
4
|
bool, for JSON booleans
float64, for JSON numbers
string, for JSON string literals
nil, for JSON null
|
要直接访问该值:
1
2
3
4
5
|
result.Type // can be String, Number, True, False, Null, or JSON
result.Str // holds the string
result.Num // holds the float64 number
result.Raw // holds the raw json
result.Index // index of raw value in original json, zero means index unknown
|
有多种方便的功能可以处理结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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类型之一:
1
2
3
4
5
6
|
boolean >> bool
number >> float64
string >> string
null >> nil
array >> []interface{}
object >> map[string]interface{}
|
result.Array()函数返回一个值数组。如果结果表示不存在的值,则将返回一个空数组。如果结果不是JSON数组,则返回值将是一个包含一个结果的数组。
64位整数
该result.Int()和result.Uint()是能够读取所有64位,允许大型JSON整数。
1
2
|
result.Int() int64 // -9223372036854775808 to 9223372036854775807
result.Uint() int64 // 0 to 18446744073709551615
|
JSON行
支持使用前缀的JSON行,..前缀将多行文档视为数组。
例如:
1
2
3
4
|
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
|
1
2
3
4
5
|
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#(name="May").age >> 57
|
ForEachLines函数将遍历JSON行。
1
2
3
4
|
gjson.ForEachLine(json, func(line gjson.Result) bool{
println(line.String())
return true
})
|
获取嵌套数组值
假设您想要来自以下json的所有姓氏:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"programmers": [
{
"firstName": "Janet",
"lastName": "McLaughlin",
}, {
"firstName": "Elliotte",
"lastName": "Hunter",
}, {
"firstName": "Jason",
"lastName": "Harold",
}
]
}
|
您将使用如下路径:"programmers.#.lastName":
1
2
3
4
|
result := gjson.Get(json, "programmers.#.lastName")
for _, name := range result.Array() {
println(name.String())
}
|
您还可以查询数组内的对象:
1
2
|
name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
println(name.String()) // prints "Elliotte"
|
遍历对象或数组
ForEach函数允许快速遍历对象或数组。key和value将传递给对象的迭代器函数。仅将值传递给数组。false从迭代器返回将停止迭代。
1
2
3
4
5
|
result := gjson.Get(json, "programmers")
result.ForEach(func(key, value gjson.Result) bool {
println(value.String())
return true // keep iterating
})
|
简单解析和获取
Parse(json)函数可以进行简单的解析,result.Get(path)可以搜索结果。
例如,所有这些将返回相同的结果:
1
2
3
|
gjson.Parse(json).Get("name").Get("last")
gjson.Get(json, "name").Get("last")
gjson.Get(json, "name.last")
|
检查值是否存在
有时您只想知道值是否存在。
1
2
3
4
5
6
7
8
9
10
11
|
value := gjson.Get(json, "name.last")
if !value.Exists() {
println("no last name")
} else {
println(value.String())
}
// Or as one step
if gjson.Get(json, "name.last").Exists() {
println("has a last name")
}
|
验证JSON
Get*和Parse*功能预计,JSON是良好的。错误的json不会panic,但它可能会返回意外结果。
如果您从不可预测的来源使用JSON,则可能需要在使用GJSON之前进行验证。
1
2
3
4
|
if !gjson.Valid(json) {
return errors.New("invalid json")
}
value := gjson.Get(json, "name.last")
|
解码map
解码map[string]interface{}:
1
2
3
4
|
m, ok := gjson.Parse(json).Value().(map[string]interface{})
if !ok {
// not a map
}
|
使用Bytes
如果您的JSON包含在[]byte切片中,则有GetBytes函数。这优先于Get(string(data), path)。
1
2
|
var json []byte = ...
result := gjson.GetBytes(json, path)
|
如果您使用的是gjson.GetBytes(json, path)函数,并且希望避免转换result.Raw为[]byte,则可以使用以下模式:
1
2
3
4
5
6
7
8
|
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。
一次获取多个值
GetMany函数可用于同时获取多个值。
1
|
results := gjson.GetMany(json, "name.first", "name.last", "age")
|
返回值为[]Result,它将始终包含与输入路径完全相同数量的项目。
性能
GJSON与encoding / json, ffjson, EasyJSON, jsonparser和json-iterator的Benchmarks
1
2
3
4
5
6
7
8
|
BenchmarkGJSONGet-8 3000000 372 ns/op 0 B/op 0 allocs/op
BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 allocs/op
BenchmarkJSONUnmarshalMap-8 600000 9019 ns/op 3048 B/op 69 allocs/op
BenchmarkJSONDecoder-8 300000 14120 ns/op 4224 B/op 184 allocs/op
BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 allocs/op
BenchmarkEasyJSONLexer-8 3000000 887 ns/op 613 B/op 6 allocs/op
BenchmarkJSONParserGet-8 3000000 499 ns/op 21 B/op 0 allocs/op
BenchmarkJSONIterator-8 3000000 812 ns/op 544 B/op 9 allocs/op
|
使用的JSON文档:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
{
"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;"
}
}
}
|
每个操作都通过以下搜索路径之一进行轮换:
1
2
3
|
widget.window.name
widget.image.hOffset
widget.text.onMouseUp
|
这些基准测试是在使用Go 1.8的MacBook Pro 15“ 2.8 GHz Intel Core i7上运行的.
参考:https://github.com/tidwall/gjson