GJSON 路径语法与取值实战:scan4all 仓库内置的高性能 JSON 解析库解析
GJSON 路径语法与取值实战scan4all 仓库内置的高性能 JSON 解析库解析【免费下载链接】scan4allOfficial repository vuls Scan: 15000PoCs; 23 kinds of application password crack; 7000Web fingerprints; 146 protocols and 90000 rules Port scanning; Fuzz, HW, awesome BugBounty( ͡° ͜ʖ ͡°)...项目地址: https://gitcode.com/GitHub_Trending/sca/scan4allGJSON 是一个以快速、简洁地从一个 JSON 文档中取值为目标的 Go 库支持一行式取值one line retrieval、点号路径dot notation paths、数组/对象迭代、JSON Lines 解析等能力。在 scan4all 仓库中GJSON 以 vendor 依赖形式内置于 vendor/github.com/tidwall/gjson/ 目录go.mod 中声明版本为github.com/tidwall/gjson v1.16.0阅读本指南后你将完整掌握 GJSON 的路径语法、Result 类型、修饰符机制与字节级读取技巧并能在任意 Go 项目中零依赖地高效提取 JSON 字段。GJSON 概览它解决什么问题在 Go 生态中标准库encoding/json需要先将 JSON 完整反序列化到 struct 或map[string]interface{}才能取值这在只关心文档中少量字段的场景下既啰嗦又低效。GJSON 的思路不同它不做完整反序列化而是直接按路径在原始 JSON 文本中定位并切片出目标值因此具备两大特性快单次取值零分配详见性能小节简单用gjson.Get(json, path)一行代码拿到结果。官方还提供了配套生态修改 JSON 可用 SJSON命令行处理可用 JJ 工具。本篇 README 定位为快速上手完整的路径语法细节见 SYNTAX.md仓库中的权威实现位于 gjson.go共 3476 行单文件实现。安装安装 Go 后执行$ go get -u github.com/tidwall/gjson该命令会拉取库到本地模块缓存。若项目采用 vendor 模式如 scan4all 这样将依赖锁定在 vendor/github.com/tidwall/gjson/ 下则直接复用 vendor 目录中的源码即可无需网络下载。快速取值Get 与点号路径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在 gjson.go 中可以看到Get(json, path string) Result的入口定义。除基础Get外还有一次取多值的 GetMany 与针对字节切片的 GetBytes。路径语法核心路径是一串以点号分隔的键。键可以包含通配符*和?访问数组元素时用索引作为键#字符用于获取数组长度或访问子路径点号与通配符可以用\转义。以下示例 JSON 贯穿全文{ 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.lastAndersonage37children[Sara,Alex,Jack]children.#3children.1Alexfriends.1.firstRoger通配符*匹配任意零个及以上字符?匹配任意单个字符路径结果child*.2Jackc?ildren.0Sara转义字符.、*、?等特殊字符可用\转义。注意在 Go 源码中硬编码路径时\本身也需要按 Go 字符串规则转义val : gjson.Get(json, fav\\.movie) // 必须转义反斜杠本身 val : gjson.Get(json, fav\.movie) // 使用原始字符串无需转义路径结果fav\.movieDeer Hunter数组与##单独使用表示取数组长度#后可继续跟子路径作用于数组的每个元素路径结果friends.#3friends.#.age[44,68,47]friends.#.first[Dale,Roger,Jane]查询#(...)与#(...)#用#(...)查询数组中第一个匹配项用#(...)#查找所有匹配项。查询支持比较运算符、!、、、、以及简单模式匹配%like和!%not like路径结果friends.#(lastMurphy).firstDalefriends.#(lastMurphy)#.first[Dale,Jane]friends.#(age45)#.last[Craig,Murphy]friends.#(first%D*).lastMurphyfriends.#(first!%D*).lastCraigfriends.#(nets.#(fb))#.first[Dale,Roger]对数组中非对象值做查询时运算符右侧可以留空省略待比较的字符串路径结果children.#(!%*a*)Alexchildren.#(%*a*)#[Sara,Jack]查询支持嵌套如上表中对nets数组的二次#(...)查询。兼容性提示v1.3.0 之前查询使用#[...]方括号语法v1.3.0 起改为#(...)以避免与多路径multipath语法混淆#[...]出于向后兼容会继续工作到下一个大版本。波浪号~布尔转换~运算符会在比较前把值转换为布尔语义支持以下比较类型~true 将真值true-ish转为 true ~false 将假值false-ish及不存在的值视为 false ~null 将 null 及不存在的值视为 true ~* 将任何存在的值转为 true例如对下面这份数据{ vals: [ { a: 1, b: data }, { 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 } ] }可以批量筛出真值、假值、null 与存在性vals.#(b~true)#.a [2,6,7,8] vals.#(b~false)#.a [3,4,5,9,10,11] vals.#(b~null)#.a [10,11] vals.#(b~*)#.a [1,2,3,4,5,6,7,8,9,10] vals.#(b!~*)#.a [11]注意最后一个a:11的对象中b不存在被~false/~null视为假值/空值但被!~*视为不存在而入选。点号 vs 管道符.是标准分隔符|在大多数情况下等价两者差异体现在#数组与查询之后的后续路径上.会在返回结果之前对每个数组元素先处理后续路径而|是在得到结果之后再对结果整体处理。friends.0.first Dale friends|0.first Dale friends.0|first Dale friends|0|first Dale friends|# 3 friends.# 3 friends.#(lastMurphy)# [{first: Dale, last: Murphy, age: 44},{first: Jane, last: Murphy, age: 47}] friends.#(lastMurphy)#.first [Dale,Jane] friends.#(lastMurphy)#|first non-existent friends.#(lastMurphy)#.0 [] friends.#(lastMurphy)#|0 {first: Dale, last: Murphy, age: 44} friends.#(lastMurphy)#.# [] friends.#(lastMurphy)#|# 2逐条拆解friends.#(lastMurphy)#单独执行得到由两个 Murphy 元素组成的数组.后缀如.first、.0、.#会先对数组每个元素施加路径再汇总元素是对象则.first有效数组.0/.#对元素无效而|后缀对整个结果数组施加路径——|first因数组上无first键而得到不存在|0取数组首元素|#取数组长度 2。修饰符与路径链v1.2 起支持修饰符函数modifier与路径链path chaining。修饰符是对 JSON 做自定义处理的路径组件多个路径可用管道符|链式连接便于从修饰后的结果中继续取值。例如对children数组使用内置reverse修饰符反转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 字符串解包group对对象数组分组dig无需提供完整路径即可搜索值。修饰符参数修饰符可接受可选参数参数可以是合法 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。自定义修饰符通过 AddModifier 可以注册自定义修饰符。例如创建一个让 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]多路径Multipathsv1.3.0 起GJSON 可以把多个路径拼接成新文档把逗号分隔的路径包在[...]或{...}中分别得到新的数组或对象。例如{name.first,age,the_murphys:friends.#(lastMurphy)#.first}这里选取了 first 名、age以及所有姓氏为 Murphy 的朋友的 first 名。可选项the_murphys:用于强制指定键名若不指定则使用实际字段名如上例的first若无法确定名字则使用_。结果为{first:Tom,age:37,the_murphys:[Dale,Jane]}JSON 字面量Literalsv1.12.0 起支持 JSON 字面量用于构造静态 JSON 块配合多路径构造新文档尤其有用。字面量以!声明字符开头{name.first,age,company:!Happysoft,employed:!true}选取 first 名与 age 后再新增company与employed两个字段结果为{first:Tom,age:37,company:Happysoft,employed:true}Result 类型与取值函数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 // 取值可以是 String, Number, True, False, Null, 或 JSON result.Str // 持有字符串 result.Num // 持有 float64 数值 result.Raw // 持有原始 json result.Index // 原始 json 中该值的索引0 表示索引未知 result.Indexes // 对包含 # 查询字符的路径返回所有匹配元素的索引Result 上提供一系列便捷函数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{}需要类型断言对应关系如下boolean bool number float64 string string null nil array []interface{} object map[string]interface{}result.Array()返回值数组若结果表示不存在的值则返回空数组若结果本身不是 JSON 数组则返回包含单个结果的数组。64 位整数result.Int()与result.Uint()能读取全部 64 位支持大整数 JSONresult.Int() int64 // -9223372036854775808 到 9223372036854775807 result.Uint() uint64 // 0 到 18446744073709551615JSON Lines 支持使用..前缀可以将多行文档当作数组处理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] ..#(nameMay).age 57逐行迭代可用 ForEachLinegjson.ForEachLine(json, func(line gjson.Result) bool{ println(line.String()) return true })嵌套数组取值实战假设要取出下面 JSON 中所有lastName{ programmers: [ { firstName: Janet, lastName: McLaughlin, }, { firstName: Elliotte, lastName: Hunter, }, { firstName: Jason, lastName: Harold, } ] }使用路径programmers.#.lastNameresult : gjson.Get(json, programmers.#.lastName) for _, name : range result.Array() { println(name.String()) }也可以查询数组内的对象name : gjson.Get(json, programmers.#(lastNameHunter).firstName) println(name.String()) // prints Elliotte迭代对象或数组ForEach用于快速遍历对象或数组对象会向迭代函数传入 key 与 value数组只传 value迭代函数返回false即停止迭代。result : gjson.Get(json, programmers) result.ForEach(func(key, value gjson.Result) bool { println(value.String()) return true // 继续迭代 })简单解析Parse 与 result.GetParse 做一次简单解析之后可对结果继续调用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()) } // 或者一步完成 if gjson.Get(json, name.last).Exists() { println(has a last name) }校验 JSON 合法性Get*与Parse*函数默认假定输入是良构 JSON遇到坏 JSON 不会 panic但可能返回意外结果。当 JSON 来自不可信来源时建议先用 Valid 校验if !gjson.Valid(json) { return errors.New(invalid json) } value : gjson.Get(json, name.last)反序列化到 map想得到map[string]interface{}时m, ok : gjson.Parse(json).Value().(map[string]interface{}) if !ok { // not a map }字节切片优化GetBytes当 JSON 存放在[]byte中时应优先使用GetBytes而非Get(string(data), path)避免字符串转换开销var json []byte ... result : gjson.GetBytes(json, path)若使用GetBytes且想避免把result.Raw再转成[]byte可以采用零分配子切片模式——利用result.Index原始 JSON 中的位置直接从原切片切出var json []byte ... result : gjson.GetBytes(json, path) var raw []byte if result.Index 0 { raw json[result.Index:result.Indexlen(result.Raw)] } else { raw []byte(result.Raw) }这是对原始 JSON 的最佳零分配子切片方案。需要注意result.Index可能为 0索引未知此时退回[]byte(result.Raw)转换。一次获取多个值GetManyGetMany 可同时获取多个值results : gjson.GetMany(json, name.first, name.last, age)返回类型为[]Result元素数量恒等于传入路径数量。性能基准官方 README 提供了 GJSON 与encoding/json、ffjson、EasyJSON、jsonparser、json-iterator 的对比基准MacBook Pro 16 2.4 GHz Intel Core i9 Go 1.17 环境测试 JSON 为经典 widget 文档操作轮流在widget.window.name、widget.image.hOffset、widget.text.onMouseUp三个路径上进行BenchmarkGJSONGet-16 11644512 311 ns/op 0 B/op 0 allocs/op BenchmarkGJSONUnmarshalMap-16 1122678 3094 ns/op 1920 B/op 26 allocs/op BenchmarkJSONUnmarshalMap-16 516681 6810 ns/op 2944 B/op 69 allocs/op BenchmarkJSONUnmarshalStruct-16 697053 5400 ns/op 928 B/op 13 allocs/op BenchmarkJSONDecoder-16 330450 10217 ns/op 3845 B/op 160 allocs/op BenchmarkFFJSONLexer-16 1424979 2585 ns/op 880 B/op 8 allocs/op BenchmarkEasyJSONLexer-16 3000000 729 ns/op 501 B/op 5 allocs/op BenchmarkJSONParserGet-16 3000000 366 ns/op 21 B/op 0 allocs/op BenchmarkJSONIterator-16 3000000 869 ns/op 693 B/op 14 allocs/op其中BenchmarkGJSONGet达到0 B/op、0 allocs/op的零分配单次取值这正是 GJSON 面向高频、仅需少量字段场景的核心优势。在 scan4all 仓库中的集成形态从仓库证据看GJSON 在 scan4all 中是以**间接依赖indirect**身份被引入的依赖声明go.mod 第 250 行github.com/tidwall/gjson v1.16.0 // indirect版本锁定go.sum 中同时记录了v1.16.0模块校验和与旧版v1.12.1的 go.mod 记录说明项目在升级过程中保留了新旧版本的哈希条目内嵌源码vendor/github.com/tidwall/gjson/gjson.go 为单文件实现3476 行与 README 中介绍的全部 API 一一对应——ParseL464、ForEachLineL1797、GetL2010、GetBytesL2123、GetManyL2334、ValidL2629、AddModifierL2822等。因此若你在阅读 scan4all 源码或在其基础上二次开发时需要对 HTTP 响应体、指纹数据等 JSON 内容做快速字段提取可直接 import 该 vendor 包并使用本文介绍的路径语法无需额外引入第三方依赖。更完整的语法定义可随时查阅 SYNTAX.md 作为速查手册。【免费下载链接】scan4allOfficial repository vuls Scan: 15000PoCs; 23 kinds of application password crack; 7000Web fingerprints; 146 protocols and 90000 rules Port scanning; Fuzz, HW, awesome BugBounty( ͡° ͜ʖ ͡°)...项目地址: https://gitcode.com/GitHub_Trending/sca/scan4all创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考