gjson:在Go中快速解析JSON


GJSON是一个Go包,它提供了一种非常快速和简单的方式从json文档中获取值。这个库的目的是为BuntDB 项目提供高效的json索引。

点击链接 JSONed查看命令行接口。

安装

要使用gjson,先要安装go环境并执行go get:

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

以上命令会检索并下载该库到Go环境中。

Get函数获取值

Get在json中搜索指定的路径。路径用点语法表示,比如“name.last"或“age"。这个函数需要提供格式正规和有效的json值。无效的json不会引起panic,但它可能返回意外的结果。当找到值后立即返回。

package main

import "github.com/tidwall/gjson"

const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`

func main() {
    result := gjson.Get(json, "name.last")
    println(result.String())
}

output

Prichard

还有用于处理JSON字节切片的GetBytes 函数。

path语法

path是由点分隔的一系列键。key可以包含特殊的通配符'*'和'?'。要访问数组值,请使用索引作为键。要获取数组中的元素数量或访问子路径,请使用'#'字符。点和通配符可以用'\'转义。

{
  "name": {"first": "Tom", "last": "Anderson"},
  "age":37,
  "children": ["Sara","Alex","Jack"],
  "fav.movie": "Deer Hunter",
  "friends": [
    {"first": "James", "last": "Murphy"},
    {"first": "Roger", "last": "Craig"}
  ]
}
"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"    >> ["James","Roger"]
"friends.1.last"     >> "Craig"

查询一个数组:

`friends.#[last="Murphy"].first` >> "James"

结果类型

GJSON支持json类型字符串,数字,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, or JSON
result.Str     // 保存string
result.Num     // 保存float64
result.Raw     // 保存 raw json
result.Index   // json中原始值的索引,0表示索引未知

result有很多函数:

result.Value() interface{}
result.Int() int64
result.Uint() uint64
result.Float() float64
result.String() string
result.Bool() bool
result.Array() []gjson.Result
result.Map() map[string]gjson.Result
result.Get(path string) Result

result.Value返回一个interface{},需要断言,是以下类型之一:

boolean >> bool
number  >> float64
string  >> string
null    >> nil
array   >> []interface{}
object  >> map[string]interface{}

result.Array()返回一个数组。如果result代表一个不存在的值,那么将返回一个空数组。如果result不是一个JSON数组,返回值将是一个包含一个结果的数组。

Get获取嵌套数组值

假设你想要以下json中的所有lastName对应值:

{
  "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())  // 输出 "Elliotte"

parse和Get

有一个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())
}

// 或一步到位
if gjson.Get(json, "name.last").Exists(){
    println("has a last name")
}
反序列化到map
m, ok := gjson.Parse(json).Value().(map[string]interface{})
if !ok{
    // 不是map
}

使用字节

如果json存在[]byte切片中,有一个 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的值可能等于0,这种情况下result.Raw被转成[]byte。

性能

与encoding/json, ffjson, EasyJSON和jsonparser,对比 GJSON的基准测试:

BenchmarkGJSONGet-8                 15000000           333 ns/op           0 B/op          0 allocs/op
BenchmarkGJSONUnmarshalMap-8          900000          4188 ns/op        1920 B/op         26 allocs/op
BenchmarkJSONUnmarshalMap-8           600000          8908 ns/op        3048 B/op         69 allocs/op
BenchmarkJSONUnmarshalStruct-8        600000          9026 ns/op        1832 B/op         69 allocs/op
BenchmarkJSONDecoder-8                300000         14339 ns/op        4224 B/op        184 allocs/op
BenchmarkFFJSONLexer-8               1500000          3156 ns/op         896 B/op          8 allocs/op
BenchmarkEasyJSONLexer-8             3000000           938 ns/op         613 B/op          6 allocs/op
BenchmarkJSONParserGet-8             3000000           442 ns/op          21 B/op          0 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容