tidwall/gjson
GitHub: tidwall/gjson
一个高性能的 Go 语言 JSON 解析库,通过简洁的点号路径语法实现快速查询和提取 JSON 文档中的值。
Stars: 15545 | Forks: 906
快速获取 json 值
GJSON 是一个 Go 包,它提供了一种[快速](#performance)且[简单](#get-a-value)的方法来从 json 文档中获取值。 它具有[单行检索](#get-a-value)、[点号路径语法](#path-syntax)、[迭代](#iterate-through-an-object-or-array)和[解析 json lines](#json-lines)等特性。 还可以查看用于修改 json 的 [SJSON](https://github.com/tidwall/sjson) 以及 [JJ](https://github.com/tidwall/jj) 命令行工具。 本 README 是一个关于如何使用 GJSON 的快速概述,欲了解更多信息,请查看 [GJSON 语法](SYNTAX.md)。 GJSON 也支持 [Python](https://github.com/volans-/gjson-py) 和 [Rust](https://github.com/tidwall/gjson.rs) 官方 Git 代码库位于 [Codeberg](https://codeberg.org/tidwall/gjson)。 # 快速入门 ## 安装 要开始使用 GJSON,请安装 Go 并运行 `go get`: ``` $ go get -u github.com/tidwall/gjson ``` 这将检索该库。 ## 获取值 Get 在 json 中搜索指定的路径。路径采用点号语法,例如 "name.last" 或 "age"。找到值后,它会立即返回。 ``` 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()) } ``` 这将输出: ``` Prichard ``` *此外还有用于处理 JSON 字节切片的 [GetBytes](#working-with-bytes)。* ## 路径语法 以下是路径语法的快速概述,如需更完整的信息,请 查看 [GJSON 语法](SYNTAX.md)。 路径是一系列由点号分隔的键。 键可以包含特殊的通配符 '*' 和 '?'。 要访问数组值,请使用索引作为键。 要获取数组中的元素数量或访问子路径,请使用 '#' 字符。 点号和通配符可以使用 '\\' 进行转义。 ``` { "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"]} ] } ``` ``` "name.last" >> "Anderson" "age" >> 37 "children" >> ["Sara","Alex","Jack"] "children.#" >> 3 "children.1" >> "Alex" "child*.2" >> "Jack" "c?ildren.0" >> "Sara" "fav\.movie" >> "Deer Hunter" "friends.#.first" >> ["Dale","Roger","Jane"] "friends.1.last" >> "Craig" ``` 你还可以使用 `#(...)` 查询数组的第一个匹配项,或者使用 `#(...)#` 查找所有匹配项。查询支持 `==`、`!=`、`<`、`<=`、`>`、`>=` 比较运算符以及简单的模式匹配 `%` (like) 和 `!%` (not like) 运算符。 ``` 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" friends.#(nets.#(=="fb"))#.first >> ["Dale","Roger"] ``` *请注意,在 v1.3.0 之前,查询使用 `#[...]` 括号。这在 v1.3.0 中被更改,以避免与新的 [multipath](SYNTAX.md#multipaths) 语法混淆。为了向后兼容, `#[...]` 将继续工作,直到下一个主要版本发布。* ## 结果类型 GJSON 支持 json 类型 `string`、`number`、`bool` 和 `null`。 数组和对象作为其原始 json 类型返回。 `Result` 类型持有以下其中之一: ``` bool, for JSON booleans float64, for JSON numbers string, for JSON string literals nil, for JSON null ``` 要直接访问该值: ``` 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. ``` 有多种可对结果进行操作的便捷函数: ``` 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 类型之一: ``` boolean >> bool number >> float64 string >> string null >> nil array >> []interface{} object >> map[string]interface{} ``` `result.Array()` 函数返回一个值数组。 如果结果表示一个不存在的值,则将返回一个空数组。 如果结果不是 JSON 数组,则返回值将是一个包含单个结果的数组。 ### 64 位整数 `result.Int()` 和 `result.Uint()` 调用能够读取全部 64 位,从而支持大型 JSON 整数。 ``` result.Int() int64 // -9223372036854775808 to 9223372036854775807 result.Uint() uint64 // 0 to 18446744073709551615 ``` ## 修饰符和路径链式操作 修饰符是一个路径组件,用于对 json 执行自定义处理。 多个路径可以使用管道符“链式”连接在一起。 这对于从修改后的查询中获取结果非常有用。 例如,在上述 json 文档上使用内置的 `@reverse` 修饰符, 我们将获取 `children` 数组并反转其顺序: ``` "children|@reverse" >> ["Jack","Alex","Sara"] "children|@reverse|0" >> "Jack" ``` 目前有以下内置修饰符: - `@reverse`:反转数组或对象的成员。 - `@ugly`:删除 json 文档中的所有空格。 - `@pretty`:使 json 文档更具人类可读性。 - `@this`:返回当前元素。它可用于检索根元素。 - `@valid`:确保 json 文档有效。 - `@flatten`:展平数组。 - `@join`:将多个对象合并为一个单独的对象。 - `@keys`:返回对象的键数组。 - `@values`:返回对象的值数组。 - `@tostr`:将 json 转换为字符串。包装一个 json 字符串。 - `@fromstr`:从 json 转换字符串。解包一个 json 字符串。 - `@group`:对对象数组进行分组。参见 [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db)。 - `@dig`:在不提供完整路径的情况下搜索值。参见 [e8e87f2](https://github.com/tidwall/gjson/commit/e8e87f2a00dc41f3aba5631094e21f59a8cf8cbf)。 ### 修饰符参数 修饰符可以接受一个可选参数。该参数可以是有效的 JSON 文档,也可以仅仅是字符。 例如,`@pretty` 修饰符接受一个 json 对象作为其参数。 ``` @pretty:{"sortKeys":true} ``` 这会美化 json 并对其所有键进行排序。 ``` { "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"} } ``` *`@pretty` 选项的完整列表包括 `sortKeys`、`indent`、`prefix` 和 `width`。 请参见 [Pretty 选项](https://github.com/tidwall/pretty#customized-output) 以获取更多信息。* ### 自定义修饰符 你也可以添加自定义修饰符。 例如,在这里我们创建一个修饰符,将整个 json 文档转换为大写 或小写。 ``` 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"] ``` ## JSON Lines 例如: ``` {"name": "Gilbert", "age": 61} {"name": "Alexa", "age": 34} {"name": "May", "age": 57} {"name": "Deloise", "age": 44} ``` ``` ..# >> 4 ..1 >> {"name": "Alexa", "age": 34} ..3 >> {"name": "Deloise", "age": 44} ..#.name >> ["Gilbert","Alexa","May","Deloise"] ..#(name="May").age >> 57 ``` `ForEachLines` 函数将遍历 JSON lines。 ``` gjson.ForEachLine(json, func(line gjson.Result) bool{ println(line.String()) return true }) ``` ## 获取嵌套数组值 假设你想要从以下 json 中获取所有的姓氏: ``` { "programmers": [ { "firstName": "Janet", "lastName": "McLaughlin", }, { "firstName": "Elliotte", "lastName": "Hunter", }, { "firstName": "Jason", "lastName": "Harold", } ] } ``` 你可以像这样使用路径 "programmers.#.lastName": ``` result := gjson.Get(json, "programmers.#.lastName") for _, name := range result.Array() { println(name.String()) } ``` 你也可以查询数组内的对象: ``` name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`) println(name.String()) // prints "Elliotte" ``` ## 遍历对象或数组 `ForEach` 函数允许快速遍历对象或数组。 对于对象,键和值会传递给迭代器函数。 对于数组,仅传递值。 在迭代器中返回 `false` 将停止迭代。 ``` 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)` 函数用于搜索结果。 例如,以下所有操作都将返回相同的结果: ``` gjson.Parse(json).Get("name").Get("last") gjson.Get(json, "name").Get("last") gjson.Get(json, "name.last") ``` ## 检查值是否存在 有时你只是想知道某个值是否存在。 ``` 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 之前进行验证。 ``` if !gjson.Valid(json) { return errors.New("invalid json") } value := gjson.Get(json, "name.last") ``` ## Unmarshal 为 map 要 unmarshal 为 `map[string]interface{}`: ``` m, ok := gjson.Parse(json).Value().(map[string]interface{}) if !ok { // not a map } ``` ## 处理 Bytes 如果你的 JSON 包含在 `[]byte` 切片中,可以使用 [GetBytes](https://godoc.org/github.com/tidwall/gjson#GetBytes) 函数。这比 `Get(string(data), path)` 更受推荐。 ``` var json []byte = ... result := gjson.GetBytes(json, path) ``` 如果你正在使用 `gjson.GetBytes(json, path)` 函数,并且想要避免将 `result.Raw` 转换为 `[]byte`,你可以使用这种模式: ``` 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`。 ## 性能 GJSON 与 [encoding/json](https://golang.org/pkg/encoding/json/)、 [ffjson](https://github.com/pquerna/ffjson)、 [EasyJSON](https://github.com/mailru/easyjson)、 [jsonparser](https://github.com/buger/jsonparser), 以及 [json-iterator](https://github.com/json-iterator/go) 的基准测试对比 ``` BenchmarkGJSONGet-10 17893731 202.1 ns/op 0 B/op 0 allocs/op BenchmarkGJSONUnmarshalMap-10 1663548 2157 ns/op 1920 B/op 26 allocs/op BenchmarkJSONUnmarshalMap-10 832236 4279 ns/op 2920 B/op 68 allocs/op BenchmarkJSONUnmarshalStruct-10 1076475 3219 ns/op 920 B/op 12 allocs/op BenchmarkJSONDecoder-10 585729 6126 ns/op 3845 B/op 160 allocs/op BenchmarkFFJSONLexer-10 2508573 1391 ns/op 880 B/op 8 allocs/op BenchmarkEasyJSONLexer-10 3000000 537.9 ns/op 501 B/op 5 allocs/op BenchmarkJSONParserGet-10 13707510 263.9 ns/op 21 B/op 0 allocs/op BenchmarkJSONIterator-10 3000000 561.2 ns/op 693 B/op 14 allocs/op ``` 使用的 JSON 文档: ``` { "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;" } } } ``` 每次操作都在以下搜索路径中轮流进行: ``` widget.window.name widget.image.hOffset widget.text.onMouseUp ``` ** *这些基准测试是在 MacBook Pro M1 Max 上使用 Go 1.22 运行的,可以在[这里](https://github.com/tidwall/gjson-benchmarks)找到。*标签:EVTX分析, 可视化界面, 日志审计, 逆向工具