Golang Gjson
1. 基本概述
相信使用过动态语言的人,都觉得解析JSON是很简单的,只需要简单的几行代码就可以拿到解析好的JSON对象。例如Python解析JSON如下所示
1import json
2jsonStr = '{"name": "Bob", "age": 18}'
3result = json.loads(jsonStr)
4print(result["name"]) "Bob"
而Golang语言中简单的数据结构可使用map[string]interface{},但如果JSON嵌套格式太复杂,用这种方式特别容易绕晕,而如果预先定义struct结构体,用json.Unmarshal把数据解析到结构体中,取出对应的数据,代码量编写会增加许多。如下所示
1type Student struct {
2 Name string `json:"name"`
3 Age int `json:"age"`
4}
5
6jsonData := []byte(`
7{
8 "name": "Bob",
9 "age": 18
10}`)
11
12var student Student
13err := json.Unmarshal(jsonData, &student)
14if err != nil {
15 fmt.Println(err)
16}
17fmt.Println(student.Name)
经过对比,发现GoLang解析JSON有没有相对简单的方法呢,作为受众这么广的语言,肯定是有解决方法的,这里就介绍一下今天要隆重介绍的第三方包GJSON。
GJSON是一个Go包,它提供了一种快速,简单的方法来从JSON文档中获取值。它具有诸如单行检索,点符号路径,迭代和解析JSON行之类的功能。项目地址: https://github.com/tidwall/gjson
GJSON 获得JSON的值,比较简单,操作如下
1package main
2
3import "github.com/tidwall/gjson"
4
5const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
6
7func main() {
8 value := gjson.Get(json, "name.last")
9 println(value.String()) Prichard
10}
经过对比,发现GJSON获得值的方式,相对简单许多。GJSON还支持简单的路径语法去获得值,具体使用方式下面会详细介绍
2. 安装
要使用GSON, 首先需要安装Go然后执行如下go get命令
1go get -u github.com/tidwall/gjson
3. 使用方式
3.1. 路径语法
以下是路径语法的快速概述,有关更多完整信息,请查看GJSON语法。路径是一系列由点分隔的键。键可能包含特殊的通配符'*'和'?'。要访问数组值,请使用索引作为键。要获取数组中的元素数或访问子路径,请使用“#”字符。点和通配符可以用'\'进行转义。
1package main
2
3
4import (
5 "fmt"
6 "github.com/tidwall/gjson"
7)
8
9const json = `
10{
11 "name": {"first": "Tom", "last": "Anderson"},
12 "age":37,
13 "children": ["Sara","Alex","Jack"],
14 "fav.movie": "Deer Hunter",
15 "friends": [
16 {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
17 {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
18 {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
19 ]
20}
21`
22
23func main() {
24 // 得到一个值
25 value1 := gjson.Get(json, "age")
26 fmt.Println(value1) // 37
27
28 value2 := gjson.Get(json, "name.last")
29 fmt.Println(value2) // Anderson
30
31 value3 := gjson.Get(json, "name.first")
32 fmt.Println(value3) // Tom
33
34 value4 := gjson.Get(json, "children")
35 fmt.Println(value4) // ["Sara","Alex","Jack"]
36
37 // children数组的总数(元素个数)
38 value5 := gjson.Get(json, "children.#")
39 fmt.Println(value5) // 3
40
41 // children数组的第二个元素
42 value6 := gjson.Get(json, "children.1")
43 fmt.Println(value6) // Alex
44
45 // *表示任意个字符(包括0个)
46 value7 := gjson.Get(json, "child*.2")
47 fmt.Println(value7) // Jack
48
49 // ?表示0个或1个字符
50 value8 := gjson.Get(json, "c?ildren.0")
51 fmt.Println(value8) // Sara
52
53 // \ 表示转义
54 value9 := gjson.Get(json, "fav\\.movie")
55 fmt.Println(value9) // Deer Hunter
56
57 value10 := gjson.Get(json, "friends.#.first")
58 fmt.Println(value10) // ["Dale","Roger","Jane"]
59
60 value11 := gjson.Get(json, "friends.1.last")
61 fmt.Println(value11) // Craig
62
63 /*
64 也可以使用#(...)查询数组中的第一个匹配项,或使用#(...)# 查找所有匹配项。
65 查询支持==,!=,<,<=,>,>= 比较运算符,以及支持%(类似)和!%(不类似)的简单模式匹配。
66 */
67
68 // 查找数组中的第一个匹配项
69 value12 := gjson.Get(json, `friends.#(last=="Murphy").first`)
70 fmt.Println(value12) // Dale
71
72 // 查找数组中的所有匹配项
73 value13 := gjson.Get(json, `friends.#(last=="Murphy")#.first`)
74 fmt.Println(value13) // ["Dale","Jane"]
75
76 // 查找friends数组中所有age大于45的last值
77 value14 := gjson.Get(json, `friends.#(age>45)#.last`)
78 fmt.Println(value14) // ["Craig","Murphy"]
79
80 // 查找friends数组中第一个first对应的值是以D开头的 last值
81 value15 := gjson.Get(json, `friends.#(first%"D*").last`)
82 fmt.Println(value15) // Murphy
83
84 // 查找friends数组中第一个first对应的值是不以D开头的 last值
85 value16 := gjson.Get(json, `friends.#(first!%"D*").last`)
86 fmt.Println(value16) // Craig
87
88 // 查找friends数组中nets数组值包含fb
89 value17 := gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`)
90 fmt.Println(value17) // ["Dale","Roger"]
91
92}
3.2. 获取嵌套数组值
1package main
2
3import (
4 "fmt"
5 "github.com/tidwall/gjson"
6)
7
8const json = `
9{
10 "programmers": [
11 {
12 "firstName": "Janet",
13 "lastName": "McLaughlin",
14 }, {
15 "firstName": "Elliotte",
16 "lastName": "Hunter",
17 }, {
18 "firstName": "Jason",
19 "lastName": "Harold",
20 }
21 ]
22}
23`
24
25func main(){
26 // 获取嵌套数组值
27 result := gjson.Get(json, "programmers.#.lastName")
28 for _, name := range result.Array() {
29 println(name.String()) prints McLaughlin Hunter Harold
30 }
31
32 // 查询数组中对象
33 name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
34 println(name.String()) prints "Elliotte"
35
36 // 遍历对象或数组
37 result.ForEach(func(key, value gjson.Result) bool {
38 println(value.String())
39 return true keep iterating
40 })
3.3. 简单的Parse和Get使用
以下三种写法会得到相同的结果
1package main
2
3import (
4 "fmt"
5 "github.com/tidwall/gjson"
6)
7
8const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
9
10func main(){
11 value1 := gjson.Parse(json).Get("name").Get("last")
12 fmt.Println(value1) Prichard
13 value2 := gjson.Get(json, "name").Get("last")
14 fmt.Println(value2) Prichard
15 value3 := gjson.Get(json, "name.last")
16 fmt.Println(value3) Prichard
17
18 // 检测值是否已经存在
19 value := gjson.Get(json, "name.last")
20 if !value.Exists() {
21 println("no last name")
22 } else {
23 println(value.String()) Prichard
24 }
25
26 // Or as one step
27 if gjson.Get(json, "name.last").Exists() {
28 println("has a last name") has a last name
29 }
30
31 // 验证是否为json
32 if !gjson.Valid(json) {
33 fmt.Println("invalid json")
34 return
35 }
36 value4 := gjson.Get(json, "name.last")
37 fmt.Println(value4)
38
39 // 将json字符串解码到map中
40 m, ok := gjson.Parse(json).Value().(map[string]interface{})
41 if !ok {
42 // not a map
43 fmt.Println("not map")
44 }
45 fmt.Println(m) map[programmers:[map[firstName:Janet lastName:McLaughlin] map[firstName:Elliotte lastName:Hunter] map[firstName:Jason lastName:Harold]]]
46
47 // 一次获取多个值
48 results := gjson.GetMany(json, "name.first", "name.last", "age")
49 fmt.Println(results) [Janet Prichard 47]
50}
3.4. 以字节方式工作
如果您的JSON包含在[]byte切片中,则存在GetBytes函数。这比Get(string(data),path)更好
1var json []byte = ...
2result := gjson.GetBytes(json, path)
如果您使用的是gjson.GetBytes(json,path)函数,并且想要避免将result.Raw转换为[]byte,则可以使用以下模式:
1var json []byte = ...
2result := gjson.GetBytes(json, path)
3var raw []byte
4if result.Index > 0 {
5raw = json[result.Index:result.Index+len(result.Raw)]
6} else {
7raw = []byte(result.Raw)
8}
4. 性能
GJSON 和encoding/json,ffjson,EasyJSON, jsonparser和json-iterator 的性能测试
1BenchmarkGJSONGet-8 3000000 372 ns/op 0 B/op 0 allocs/op
2BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 allocs/op
3BenchmarkJSONUnmarshalMap-8 600000 9019 ns/op 3048 B/op 69 allocs/op
4BenchmarkJSONDecoder-8 300000 14120 ns/op 4224 B/op 184 allocs/op
5BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 allocs/op
6BenchmarkEasyJSONLexer-8 3000000 887 ns/op 613 B/op 6 allocs/op
7BenchmarkJSONParserGet-8 3000000 499 ns/op 21 B/op 0 allocs/op
8BenchmarkJSONIterator-8 3000000 812 ns/op 544 B/op
JSON 数据使用如下
1{
2 "widget": {
3 "debug": "on",
4 "window": {
5 "title": "Sample Konfabulator Widget",
6 "name": "main_window",
7 "width": 500,
8 "height": 500
9 },
10 "image": {
11 "src": "Images/Sun.png",
12 "hOffset": 250,
13 "vOffset": 250,
14 "alignment": "center"
15 },
16 "text": {
17 "data": "Click Here",
18 "size": 36,
19 "style": "bold",
20 "vOffset": 100,
21 "alignment": "center",
22 "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
23 }
24 }
25}
每个操作都通过以下搜索路径之一进行轮换:
widget.window.name
widget.image.hOffset
widget.text.onMouseUp
这些基准测试是基于MacBook Pro 15" 2.8 GHz Intel Core i7 和Go 1.8版本
本文翻译自GJSON的Readme文档,每个Demo都经过测试,有疑问可以反馈联系,另外GJSON通过对路径进行匹配,有兴趣的可以看一下JSONPATH相关文章:https://goessner.net/articles/JsonPath/