簡介
在前面兩篇文章中,我們分別介紹了快速讀取 JSON 值的庫gjson
和快速設定 JSON 值的庫sjson
。今天我們介紹它們的作者tidwall的一個基於gjson
和sjson
的非常實用的命令列工具jj
。它是使用 Go 編寫的快速讀取和設定 JSON 值的命令列程式。
快速使用
Mac 上可以直接使用brew install tidwall/jj/jj
安裝。其他系統可以通過下載編譯好的可執行程式,下載地址為github.com/tidwall/jj/…。
我選擇使用go get
安裝:
$ go get github.com/tidwall/jj/cmd/jj
複製程式碼
上面命令執行完成之後,編譯生成的jj
程式會放在$GOPATH/bin
目錄中,我習慣把$GOPATH/bin
加入系統可執行目錄$PATH
中,故可以直接使用。
簡單的讀取和設定(我的環境為 Win10 + Git Bash):
$ echo '{"name":{"first":"li","last":"dj"}}' | jj name.last
dj
$ echo '{"name":{"first":"li","last":"dj"}}' | jj -v dajun name.last
{"name":{"first":"li","last":"dajun"}}
複製程式碼
通過鍵路徑來指定讀取/設定的位置,上面第一個命令讀取欄位name.last
,返回dj
。
-v
選項指定設定的值。第二個命令將欄位name.last
設定為dajun
,輸出設定之後的 JSON 串。鍵路徑在前兩篇文章中有詳細的介紹,不熟悉的可以回去看一下。
讀取和設定
實際上讀取和設定的語法和形式與我們前面介紹gjson
和sjson
提到的基本一樣,只不過是在命令列上完成的而已。
讀取不存在的欄位,返回null
:
$ echo '{"name":{"first":"li","last":"dj"}}' | jj name.middle
null
複製程式碼
讀取一個物件型別的欄位,返回該物件的 JSON 表示:
$ echo '{"name":{"first":"li","last":"dj"}}' | jj name
{"first":"li","last":"dj"}
複製程式碼
使用索引(從 0 開始)讀取陣列的元素,非法的索引將返回空:
$ echo '{"fruits":["apple","orange","banana"]}' | jj fruits.1
orange
$ echo '{"fruits":["apple","orange","banana"]}' | jj fruits.3
複製程式碼
使用索引設定陣列的元素,下面命令將陣列fruits
的第二個元素設定為pear
:
$ echo '{"fruits":["apple","orange","banana"]}' | jj -v pear fruits.1
{"fruits":["apple","pear","banana"]}
複製程式碼
使用-1
或陣列長度作為索引,可以在陣列後新增一個元素。如果索引超過了陣列長度,則會多一定數量的null
:
$ echo '{"fruits":["apple","orange","banana"]}' | jj -v strawberry fruits.-1
{"fruits":["apple","orange","banana","strawberry"]}
$ echo '{"fruits":["apple","orange","banana"]}' | jj -v grape fruits.3
{"fruits":["apple","orange","banana","grape"]}
$ echo '{"fruits":["apple","orange","banana"]}' | jj -v watermelon fruits.5
{"fruits":["apple","orange","banana",null,null,"watermelon"]}
複製程式碼
使用選項-D
刪除指定鍵路徑上的元素,如果對應元素不存在,則無效果:
$ echo '{"name":"dj","age":18}' | jj -D age
{"name":"dj"}
$ echo '{"fruits":["apple","orange","banana"]}' | jj -D fruits.2
{"fruits":["apple","orange"]}
$ echo '{"fruits":["apple","orange","banana"]}' | jj -D fruits.5
{"fruits":["apple","orange","banana"]}
複製程式碼
第 1 個命令刪除欄位age
;第 2 個命令刪除陣列fruits
的第 2 個元素;第 3 個命令刪除陣列fruits
的第 5 個元素,由於陣列長度只有 3,故無效果。
檔案
jj
支援從檔案中讀取 JSON 串和將結果寫到檔案中。使用選項-i
指定輸入檔案,選項-o
指定輸出檔案。下面將從檔案fruits.txt
中讀取 JSON 串,取陣列的第 2 個元素,寫到out.txt
中:
$ jj -i fruits.txt -o out.txt fruits.1
複製程式碼
fruits.txt
的檔案內容如下:
{"fruits":["apple","orange","banana"]}
複製程式碼
執行命令,輸出檔案的內容為:
orange
複製程式碼
格式化
jj
支援將輸出的 JSON 串進行一定的格式化。選項-u
移除所有的空白符,節省儲存空間。選項-p
美化格式,便於閱讀。
$ echo '{"name":{"first": "li", "last":"dj"}, "age":18}' | jj -u name
{"first":"li","last":"dj"}
$ echo '{"name":{"first": "li", "last":"dj"}, "age":18}' | jj -p name
{
"first": "li",
"last": "dj"
}
複製程式碼
效能
與另一個 JSON 的命令列工具jq
相比,jj
是其效能的 10 倍以上。因為jj
不會驗證 JSON 串的有效性,並且它只關心鍵路徑指定的值,一旦該值處理完成就停止。這裡有效能對比:github.com/tidwall/jj#…
用途
jj
一個很方便的用途在於日誌處理,當前很多日誌庫都支援 JSON 的格式,例如前面我們介紹的logrus
。我們可以使用jj
在這些日誌中找到相應的資訊。我們先用logrus
生成 20 條玩家登陸和下線的日誌:
package main
import "github.com/sirupsen/logrus"
func main() {
logrus.SetFormatter(&logrus.JSONFormatter{})
for i := 1; i <= 10; i++ {
logrus.WithFields(logrus.Fields{
"userid": i,
}).Info("login")
logrus.WithFields(logrus.Fields{
"userid": i,
}).Info("logoff")
}
}
複製程式碼
生成日誌儲存在log.txt
檔案中:
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":1}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":1}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":2}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":2}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":3}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":3}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":4}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":4}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":5}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":5}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":6}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":6}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":7}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":7}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":8}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":8}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":9}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":9}
{"level":"info","msg":"login","time":"2020-03-26T23:36:04+08:00","userid":10}
{"level":"info","msg":"logoff","time":"2020-03-26T23:36:04+08:00","userid":10}
複製程式碼
由於每一行都是一個單獨的 JSON 串,我們可以使用jj
支援的 JSON 行特性,使用..
路徑標識這些行。..
使得jj
將這些行看成陣列的元素。我們可以讀取這些陣列元素。
獲取陣列長度,返回 20:
$ jj -i log.txt ..#
20
複製程式碼
只讀取每一行中的userid
資訊:
$ jj -i log.txt ..#.userid
[1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10]
複製程式碼
只讀取每一行中的msg
資訊:
$ jj -i log.txt ..#.msg
["login","logoff","login","logoff","login","logoff","login","logoff","login","logoff","login","logoff","login","logoff","login","logoff","login","logoff","login","logoff"]
複製程式碼
更復雜一點的,如果我們要檢視所有userid=1
的日誌:
$ jj -i log.txt ..#\(userid=1\)# -p
[
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 1
},
{
"level": "info",
"msg": "logoff",
"time": "2020-03-26T23:36:04+08:00",
"userid": 1
}
]
複製程式碼
上面的命令注意兩點,(
和)
是 shell 中的特殊字元,需要\
轉義。命令中我們使用-p
選項使結果更易讀。
如果我們只需要查詢第一條符合條件的日誌,則可以去掉最右側的#
:
$ jj -i log.txt ..#\(userid=1\) -p
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 1
}
複製程式碼
如果要檢視所有的登入資訊:
$ jj -i log.txt ..#\(msg="login"\)# -p
[
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 1
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 2
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 3
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 4
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 5
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 6
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 7
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 8
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 9
},
{
"level": "info",
"msg": "login",
"time": "2020-03-26T23:36:04+08:00",
"userid": 10
}
]
複製程式碼
總結
jj
是一個非常使用的 JSON 命令列工具,效能超讚。執行jj -h
去看看其他選項吧。
大家如果發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue?
參考
- jj GitHub:github.com/tidwall/jj
- Go 每日一庫 GitHub:github.com/darjun/go-d…
我
我的部落格:darjun.github.io
歡迎關注我的微信公眾號【GoUpUp】,共同學習,一起進步~