前言

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())
}
1
Prichard

还有一个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每个阵列元素上路径之前将结果返回。变成

1
["Dale","Jane"]

但是|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, prefixwidth

自定义修饰符

您还可以添加自定义修饰符。

例如,在这里我们创建一个修饰符,使整个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类型stringnumberbool,和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