手上有 json 檔想轉成 csv 嗎? 讓我用一個 (幾乎是) geojson 格式的檔案 stops-151.geojson 來作示範。 這是一個 "已拆掉最外層" 的 geojson 檔, 只剩 Feature 陣列, 每個圖徵是一個 Point, 代表台中市 (舊的) 151 號公車路線上的一個站牌。 對啦, 像這種很明確的小事, 現在都交給 AI 就好了。 人類只需要知道有 json 轉檔萬用瑞士刀 jq 這樣的工具, 跟 AI 的對話效率就已經可以很高。 但是我還是喜歡用部落格留下記錄, 將來用搜尋取回參考, 會比再問一次 AI 節省能源, 而且可以順便做做頭腦體操。
一、 攤平
這裡不是買股票想加碼攤平成本, 而是要把一層又一層很深的 nested object 結構攤開成淺淺一層 object。 如果你的 json 檔本來就只是一個扁平物件陣列 (array of flat objects), 也就是只有簡單的兩層結構, 則可以直接跳到下一節。
jq '.[] | .properties + .geometry' stops-151.geojson
jq '.[] | .properties + (.geometry.coordinates as $c | .)' stops-151.geojson
jq '.[] | .properties + (.geometry | .coordinates as $c | del(.coordinates) + {longitude: $c[0], latitude: $c[1]} )' stops-151.geojson | jq -s . > flat-151.json
從這幾句學到:
- 在一個陣列 (例如此處的 .) 的後面加 "[]", 可將陣列展開為獨立的 sequence/stream (of objects) (jq 手冊用語)。 這讓後續步驟的思考變簡單了, 聚焦於一個元素即可。
- 兩個 objects 可以用 "+" 合併成一個;
- "as" 可以設定變數;
- "del(...)" 可以刪除 object 的欄位。
最後一句的前半部 (pipe 之前), 會產生 a sequence/stream of objects,
也就是一長串沒有用逗點分開的 objects。
後半部的 jq -s . 目的是要把它們重新包裝成一個標準的 json 陣列,
成為 an array of objects。
二、 扁平的 object 陣列轉成 csv
注意: 本節假設輸入資料裡每個 object 的 key 結構與順序完全一致!! 上節已產生一個 "扁平" 的 object 陣列, 長這樣:
[
{
"id": "0019",
"name": "市警局",
"seq": 1,
"type": "Point",
"longitude": 120.64759,
"latitude": 24.15935
},
{
"id": "0017",
"name": "臺中市政府",
"seq": 3,
"type": "Point",
"longitude": 120.64692,
"latitude": 24.16435
},
...
{
"id": "3246",
"name": "朝陽科技大學",
"seq": 51,
"type": "Point",
"longitude": 120.71551,
"latitude": 24.07041
}
]
首先產生 csv 的標題列:
jq -r '.[0] | keys_unsorted | @csv' flat.json > stops-151.csv
首先以最前面那個元素 ".[0]" (可能是路線的起站吧) 的所有 key 名稱來產生標題列:
"keys_unsorted" 會產出一個陣列, 內容為這個 object 的所有的鍵, 按照原來的順序出現。
後面的 @csv 會把它接收到的每一個陣列當成 csv 的一列來輸出, 於是產生了一列標題列。
使用 @csv 輸出時, jq 的命令列上一律加上 "-r" 選項,
避免它試圖透過加上倒斜線來 "閃躲" (正式用語: "跳脫"、 escape) 引號。
再來產生 csv 的實質內容:
jq -r '.[] | [ .[] ] | @csv' flat.json >> stops-151.csv
先用 ".[]" 把 an array of objects 拆成一個 a sequence/stream of objects,
於是接下來的思考只需要以 (每) 一個 object (一個站牌) 為單位。
當 '.' 是一個 object 時, '.[]' 會取得所有欄位的值, 成為一個 sequence/stream。
外面包上 '[ ... ]', 把此一站牌的所有欄位值建構成一個陣列、 餵給 @csv, 就成為描述此一站牌的一列資料。
最後, 把第二節的步驟寫成一個 bash script, 叫做 flatjson2csv.sh 好了:
#!/bin/bash
jq -r '.[0] | keys_unsorted | @csv' $1
for json_file in "$@" ; do
jq -r '.[] | [ .[] ] | @csv' "$json_file"
done
這可以批次處理多個 「欄位完全相同」 的 json 檔, 把它們合併成一個 csv:
flatjson2csv.sh flat-151.json flat-200.json flat-50.json
三、 結語
以上是從 這篇 以及 ChatGPT 的解說學到的。 對照: zq 版的 json 轉 csv, 你偏好哪一個呢?
最後, 如果你沒有把握 json 檔內是否每個 object 內含的欄位都跟第
0 個 object 一樣、 不多不少而且順序完全相同, 又或者如果你有很大的
json 檔或很多個 json 檔, 那或許比較實際的做法是改用 python 版:
flatjson2csv.py 。
呼叫方式: python3 flatjson2csv.py -n route flat-151.json flat-132.json flat-131.json flat-158.json
以這個例子而言, 它會先把所有檔案內的 objects 巡過一遍,
根據它們的欄位聯集建立 csv 檔, 並且會在最前面加上一個名為 route 的欄位,
其值則為每列資料來源的檔案名稱 (不含 .json)。
遇有欠缺欄位的 object, 則會正確地留空。
大人問小孩: 「全世界的玩具隨便你挑? 這怎麼可能?
如果我要的玩具只有一個, 正好又被別人借走了呢?」
沒有留言:
張貼留言
因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。