2022年12月11日 星期日

用 zq 處理 json 檔第二層陣列的語法

zq 簡介文 當中我們用 over 來簡單處理 json 檔裡面的一層陣列; 今天我學會處理第二層陣列的方法。 今天的測試資料檔是 「台中市公車的所有路線及所有停靠站」。 如果你有自己的 tdx 服務 的帳號, 可以把下面這段裡面的 「私密目錄」、 「用戶ID」、 「用戶密碼」 及 「都市名稱」 四個地方改成自己合適/想要的值。 如果沒有 tdx 的帳號, 也可以直接下載 我預先抓回來的版本 並解壓縮。

### 如果有自己的 TDX 帳密: ###
export TDX_TOKEN_DIR=$HOME/某個私密目錄
curl -X POST --url https://tdx.transportdata.tw/auth/realms/TDXConnect/protocol/openid-connect/token -H content-type:application/x-www-form-urlencoded -d grant_type=client_credentials -d client_id=uuuuuu-xxxxxxxx-xxxx-xxxx -d client_secret=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx > $TDX_TOKEN_DIR/tdx-credential.json

export TDX_ACCESS_TOKEN=$(jq .access_token $TDX_TOKEN_DIR/tdx-credential.json | sed 's/"//g')
curl -H 'accept: application/json' -H "authorization: Bearer $TDX_ACCESS_TOKEN" "https://tdx.transportdata.tw/api/basic/v2/Bus/DisplayStopOfRoute/City/Taichung" > ~/taichung-bus-rte-stp-stn.json

### 如果沒有自己的 TDX 帳密,就直接下載我備妥的檔案: ###
wget http://fs.cyut.edu.tw/gregslab/22/taichung-bus-rte-stp-stn.json.gz
gunzip taichung-bus-rte-stp-stn.json.gz

首先用 jq 排版檔案: jq . taichung-bus-rte-stp-stn.json > tcbus.json 並且用文字編輯器打開來研究一下它的結構。 第一層陣列的每個元素代表一條公車路線。 我們有興趣的是 RouteUID (公車路線代號) 這個欄位, 以及 Stops (停靠站牌) 這個欄位。 停靠站牌本身又是一個陣列, 我們只對每個站牌裡面的 StationID (車站代號) 那個欄位有興趣。 RouteUID 跟 StationID 這兩個欄位可以讓我們產生 「路線代號」 與 「車站代號」 的多對多 (many-to-many) 對照表。

第一個簡單的做法是產生每列只有兩個欄位的 csv 檔:
zq -f csv -i json 'over this | over Stops with RouteUID => ({rte:RouteUID, stn:StationID})' tcbus.json
詳見 zq/zed 手冊 裡面的 "Lateral Subqueries" 那一節。 "with" 後面的一小段用來把外層的欄位名稱 (RouteUID) 帶進來內層使用。 "=>" 後面一定要有小括弧, 括弧裡面就是想要列印的運算式。

第二種做法把同一路線的所有站牌代號全部放在同一列, 前面再加上路線代號:
zq -f csv -i json 'over this | { RouteUID, Stations: (over Stops | yield int64(StationID) ) }' tcbus.json | perl -pe 's/^(\w+),"\[/$1: /; s/\]"$//'
這樣的輸出格式反而比較不方便逆向查詢 (車站代號 => 所有經過路線); 好處是它的列數比較少, 可以算是原始資料的精簡摘要。 總之這篇的重點是要練習語法嘛~ 這裡特別注意到第二層的 over 子句並不是接在主要的 pipe 後面, 而是 內嵌 在第一層 over 子句的回傳值的某個欄位裡面, 本身構成了一個陣列。 至於用 int64 做 type cast (型態轉換) 的目的, 是要避免 zq 印出雙引號。 這樣後續要用 regular expressions 處理比較方便。

最終目的是想要做反向查詢的對照表 (車站代號 => 所有經過路線); 透過 StationID 這個欄位抓出經過某個車站的所有路線。 最終決定還是用 python 來寫比較方便。 不過順便學會 zq 的用法, 當然要筆記一下。

沒有留言:

張貼留言

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。