相关文章推荐
温文尔雅的烤面包  ·  @Override public ...·  2 月前    · 
阳光的枇杷  ·  vc++ scrollbar变颜色 - ...·  3 月前    · 
健壮的鸵鸟  ·  java ...·  8 月前    · 
兴奋的冲锋衣  ·  Detecting if SwiftUI ...·  1 年前    · 
冷静的黄花菜  ·  Madrix Luna 8 - 8 x ...·  1 年前    · 

go语言学习之json处理工具gjson

孤单的生命旅程,一个人无奈的行走,不在回头奢望,迷失在黑暗的角落,悟情,沉思,反醒,研讨,一切都是错,只因太痴,太傻,苦苦的爱着,伤了自己,痛了自己,然而,最终何所求!

Posted by yishuifengxiao on 2023-02-21

一 快速安装

GJSON路径是一种文本字符串语法,它描述了从JSON有效载荷中快速检索值的搜索模式。

GJSON路径易于表示为一系列由字符分隔的组件。

除了字符之外,还有一些字符具有特殊含义: , . | # @ \ * ! ?

当json的key里包含特殊字符且需要根据key进行提取时,需要进行转义操作

安装命令如下

1
go get -u github.com/tidwall/gjson

github的地址为 https://github.com/tidwall/gjson

语法介绍可见 https://github.com/tidwall/gjson/blob/master/SYNTAX.md

该语法可以在 GJSON Playground https://gjson.dev/ 上在线运行查看结果

使用的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"]}
]
}

二 基本使用

2.1 快速启动

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

在许多情况下,您只需要按对象名称或数组索引检索值。

对于实例json,以下语法对应的结果如下:

1
2
3
4
5
6
7
8
9
10
11
name.last              "Anderson"
name.first "Tom"
age 37
children ["Sara","Alex","Jack"]
children.0 "Sara"
children.1 "Alex"
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"]}]
friends.1 {"first": "Roger", "last": "Craig", "age": 68}
friends.1.first "Roger"
friends.#.first ["Dale","Roger","Jane"]
friends.*.first 无结果

2.2 结果类型

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
6
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
result.Indexes // indexes of all the elements that match on a path containing the '#' query character.

有多种方便的函数可以处理结果:

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数组,则返回值将是包含一个结果的数组。

2.3 64位整数

result.Int() result.Uint() 调用能够读取所有64位,允许使用大型JSON整数。

1
2
result.Int() int64    // -9223372036854775808 to 9223372036854775807
result.Uint() uint64 // 0 to 18446744073709551615

2.4 Simple Parse and Get

可以用 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")

2.5 检查值是否存在

有时你只想知道一个值是否存在。

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")
}

2.6 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")

2.7 转换为map

将json转换为 map[string]interface{} 的结构

1
2
3
4
m, ok := gjson.Parse(json).Value().(map[string]interface{})
if !ok {
// not a map
}

2.8 使用字节

如果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

2.9 同时获取多个值

GetMany 函数可用于同时获取多个值。

1
results := gjson.GetMany(json, "name.first", "name.last", "age")

返回值是一个 []Result ,它将始终包含与输入路径完全相同数量的项。

三通配符

key可以包含特殊通配符和 . * ?

1
2
child*.2               "Jack"
c?ildren.0 "Sara"

三 转义

特殊字符需要转义才能使用

1
fav\.movie             "Deer Hunter"

在源代码中硬编码路径时,还需要确保字符正确转义

1
2
3
// Go
val := gjson.Get(json, "fav\\.movie") // must escape the slash
val := gjson.Get(json, `fav\.movie`) // no need to escape the slash
1
2
3
// Rust
let val = gjson::get(json, "fav\\.movie") // must escape the slash
let val = gjson::get(json, r#"fav\.movie"#) // no need to escape the slash

四 数组

4.1 基本使用

# 字符允许深入JSON数组,也可以使用 # 获取数组的长度

1
2
friends.#              3
friends.#.age [44,68,47]

对于以下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"

4.2 遍历对象或数组

ForEach 函数允许快速遍历对象或数组。键和值被传递给对象的迭代器函数。仅为数组传递值。从迭代器返回 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
})

五 查询

5.1 基本查询

可以使用 #(...) 查询数组的第一个符合条件的结果或者使用 #(...)# 查询全部符合条件的结果。查询语法支持 == , != , < , <= , > , >= 比较运算符,简单的模式匹配支持 % (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"]

5.2 嵌套查询

1
friends.#(nets.#(=="fb"))#.first  >> ["Dale","Roger"]

请注意,在v1.3.0之前,查询使用 #[…] 括号。为了避免与新的多路径语法混淆,v1.3.0中对此进行了更改。为了向后兼容, #[…] 将继续工作直到下一个主要版本。

~ (波浪号)运算符将在比较之前将值转换为布尔值。

l例如对下面的json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"vals": [
{ "a": 1, "b": true },
{ "a": 2, "b": true },
{ "a": 3, "b": false },
{ "a": 4, "b": "0" },
{ "a": 5, "b": 0 },
{ "a": 6, "b": "1" },
{ "a": 7, "b": 1 },
{ "a": 8, "b": "true" },
{ "a": 9, "b": false },
{ "a": 10, "b": null },
{ "a": 11 }
]
}

可以查询所有true(ish)或false(ish)值:

1
2
vals.#(b==~true)#.a    >> [1,2,6,7,8]
vals.#(b==~false)#.a >> [3,4,5,9,10,11]

最后一个不存在的值被视为false

5.3 构造新json

使用选择器语法将多个路径连接到新的JSON文档中。

1
2
{name.first,age,"murphys":friends.#(last="Murphy")#.first}
[name.first,age,children.0]

六 点 和 管道符

. 是标准分隔符,但也可以使用 | ,在大多数情况下,它们都会返回相同的结果.

主要注意的是,在使用 # 查询数组时, | . 的用法是不同的

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不存在。

然而,|0后缀返回

1
{"first": "Dale", "last": "Murphy", "age": 44}

因为0是上一个结果的第一个索引。

七 修饰符

7.1 内置修饰符

修饰符是对JSON执行自定义处理的路径组件。

例如,在上面的JSON负载上使用内置的 @reverse 修饰符将反转子数组:

1
2
children.@reverse                   ["Jack","Alex","Sara"]
[email protected] "Jack"

内置修饰符的含义如下:

  • @reverse :反转数组或对象的成员。
  • @ugly :从 JSON 中删除所有空格。
  • @pretty :使 JSON 更具人类可读性。
  • @this :返回当前元素。它可用于检索根元素。
  • @valid :确保 JSON 文档有效。
  • @flatten :展平数组。
  • @join :将多个对象合并为一个对象。
  • @keys :返回对象的键数组。
  • @values :返回对象的值数组。
  • @tostr :将 json 转换为字符串。包装 json 字符串。
  • @fromstr :从 json 转换字符串。解包 json 字符串。
  • @group :对对象数组进行分组。请参阅 e4fc67c
  • 7.2 修饰符参数

    修饰符可以接受可选参数。参数可以是有效的JSON负载,也可以是字符。

    例如,修饰符将json对象作为 @pretty 的参数

    1
    @pretty:{"sortKeys":true}

    这使得json很漂亮,并对其所有键进行排序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "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"}
    }
    1
    2
    3
    4
    5
    children.@reverse                          >> ["Jack","Alex","Sara"]
    [email protected] >> "Jack"
    {name.first,"murphys":friends.0}.@pretty >> beautiful JSON
    {name.first,"murphys":friends.0}.@ugly >> compact JSON
    [@this].#(age>35).name.last >> "Anderson"

    7.3 自定义修饰符

    可以添加自定义修改器。

    例如这里我们创建了一个修饰符,它使整个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"]

    自定义修改器在Rust版本中尚不可用

    八 多路径

    从v1.3.0开始,GJSON增加了将多个路径连接在一起以形成新文档的功能。在或之间包装逗号分隔的路径将分别产生一个新的数组或对象

    1
    {name.first,age,"the_murphys":friends.#(last="Murphy")#.first}

    在这里,我们为姓为“Murphy”的朋友选择了名字、年龄和名字。

    您将注意到,可以提供一个可选的键,在本例中为“the murphys”,以强制将键分配给一个值。否则,将使用实际字段的名称,在本例中为“first”。如果无法确定名称,则使用“ ”。

    运行结果如下:

    1
    {"first":"Tom","age":37,"the_murphys":["Dale","Jane"]}

    九 常量

    从v1.12.0开始,GJSON增加了对json文本的支持,这提供了一种构造json静态块的方法。这在使用多路径构建新的json文档时尤其有用。

    json文本以“!”开头声明字符。

    例如,使用给定的多路径:

    1
    {name.first,age,"company":!"Happysoft","employed":!true}

    这里我们选择了名字和年龄。然后添加两个新字段,“company”和“employed”。

    运行结果为

    1
    {"first":"Tom","age":37,"company":"Happysoft","employed":true}

    十 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
    })
    1. 1. 一 快速安装
    2. 2. 二 基本使用
      1. 2.1. 2.1 快速启动
      2. 2.2. 2.2 结果类型
      3. 2.3. 2.3 64位整数
      4. 2.4. 2.4 Simple Parse and Get
      5. 2.5. 2.5 检查值是否存在
      6. 2.6. 2.6 json验证
      7. 2.7. 2.7 转换为map
      8. 2.8. 2.8 使用字节
      9. 2.9. 2.9 同时获取多个值
    3. 3. 三通配符
    4. 4. 三 转义
    5. 5. 四 数组
      1. 5.1. 4.1 基本使用
      2. 5.2. 4.2 遍历对象或数组
    6. 6. 五 查询
      1. 6.1. 5.1 基本查询
      2. 6.2. 5.2 嵌套查询
      3. 6.3. 5.3 构造新json
    7. 7. 六 点 和 管道符
    8. 8. 七 修饰符
      1. 8.1. 7.1 内置修饰符
      2. 8.2. 7.2 修饰符参数
      3. 8.3. 7.3 自定义修饰符
    9. 9. 八 多路径
    10. 10. 九 常量
    11. 11. 十 JSON行