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/