カテゴリー
Jq&AwkコマンドでJSONファイル⇔CSVファイルに相互変換する方法を考察
※ 当ページには【広告/PR】を含む場合があります。
2023/06/28
前置き〜Jqコマンドのテクニックの復習
Jqコマンドの「to_entries」
$ echo '{"a": 1, "b": 2}' | jq 'to_entries'
[
{
"key": "a",
"value": 1
},
{
"key": "b",
"value": 2
}
]
#👇入力が配列の場合、key値は0から始まる番号が割り当てられる
$ echo '[{"a": 1, "b": 2}]' | jq 'to_entries'
[
{
"key": 0,
"value": {
"a": 1,
"b": 2
}
}
]
to_enties
.a
.b
#👇cのVALUEはJSONオブジェクトのまま維持される
$ echo '{"a": 1, "b": 2, "c": {"d": 3}}' | jq 'to_entries'
[
{
"key": "a",
"value": 1
},
{
"key": "b",
"value": 2
},
{
"key": "c",
"value": {
"d": 3
}
}
]
Jqコマンドの「from_entries」
to_entries
$ echo '[{"key":"a", "value":1}, {"key":"b", "value":2}]' | jq 'from_entries'
{
"a": 1,
"b": 2
}
$ echo '[{"key":"a", "value":1}, {"key":"b", "value":{"c": 3}}]' | jq 'from_entries'
{
"a": 1,
"b": {
"c": 3
}
}
from_entries
#👇[{"a":1}]をKEY-VALUE化したもの
$ echo '[{"key": 0, "value": {"a": 1}}]' | jq 'from_entries'
jq: error (at <stdin>:1): Cannot use number (0) as object key
$ echo '[{"key": "a", "value": 1}]' | jq 'from_entries | [.]'
[
{
"a": 1
}
]
to_enties
#👇{"a":1, "b":{"c": 3}}とはならないことに注意
$ echo '[{"key":"a", "value":1}, {"key":"b", "value":[{"key":"c", "value":3}]}]' | jq 'from_entries'
{
"a": 1,
"b": [
{
"key": "c",
"value": 3
}
]
}
CSVからJSONへ変換する
CSVから復元できるJSONオブジェクト
1. CSVファイルの1行目(ヘッダ行)に変数名を定義する
2. データはCSVファイルの2行目以降から開始する
3. 空のデータのフィールドは除外し、NULLとは区別する
hoge,piyo,fuga,moga,buyo
120,こんにちは,3.14,ごきげんだぜ,false
54,コンバンワ,-8.07,,true
-89,てやんでぇ,1.2E-3,さいあくだ,
👇JSON変換後
[
{
"hoge": 120,
"piyo": "こんにちは",
"fuga": 3.14,
"moga": "ごきげんだぜ",
"buyo": false
},
{
"hoge": 54,
"piyo": "コンバンワ",
"fuga": -8.07,
"buyo": true
},
{
"hoge": -89,
"piyo": "てやんでぇ",
"fuga": 0.0012,
"moga": "さいあくだ"
}
]
CSVからJSONへの変換スクリプトを実装する
$ CSV_TEXT=$(cat << EOF
hoge,piyo,fuga,moga,buyo
120,こんにちは,3.14,ごきげんだぜ,false
54,コンバンワ,-8.07,,true
-89,てやんでぇ,1.2E-3,さいあくだ,
EOF
)
#👇AwkスクリプトでKEY-VALUEパターンを構築してJSONへ変換させる
$ echo "$CSV_TEXT" | awk -F"," '
#👇ヘッダ行からフィールド名(KEY)を取得
NR == 1 {
rowCount = 0;
#👇keyという配列に値をセット
for (i = 1; i <= NF; i++) {key[i] = $i;}
}
#👇データ行(2行目以降)からデータ値(VALUE)を取得
NR > 1 {
rowCount++;
for (i = 1; i <= NF; i++) {
if ($i != "") {
if ($i ~ /^[+-]?([0-9]+[.]?[0-9]*|[.][0-9]+)([eE][+-]?[0-9]+)?$/) {
#👇数値にマッチした場合
value[rowCount][i] = "{\"key\":\"" key[i] "\",\"value\":" $i "}";
}
else if (tolower($i) ~ /(true|false|null)/) {
#👇BooleanかNULLにマッチした場合
value[rowCount][i] = "{\"key\":\"" key[i] "\",\"value\":" $i "}";
}
else {
#👇その他はすべて文字列として認識
value[rowCount][i] = "{\"key\":\"" key[i] "\",\"value\":\"" $i "\"}";
}
}
}
}
END {
for (j=1;j<=rowCount;j++) {
rslt = "[";
for (i = 1; i <= NF; i++) {
#👇VALUE配列の中身が空だったらスキップ
if (value[j][i] == null) { continue; }
else if (i == NF) { rslt = rslt value[j][i]; }
else { rslt = rslt value[j][i] ","; }
}
#👇最後に不要なコンマが残っていた場合には除去
sub(/,$/, "", rslt);
print rslt "]";
}
}
' | jq 'from_entries' | jq -s '.'
#👇出力結果
[
{
"hoge": 120,
"piyo": "こんにちは",
"fuga": 3.14,
"moga": "ごきげんだぜ",
"buyo": false
},
{
"hoge": 54,
"piyo": "コンバンワ",
"fuga": -8.07,
"buyo": true
},
{
"hoge": -89,
"piyo": "てやんでぇ",
"fuga": 0.0012,
"moga": "さいあくだ"
}
]
from_entries
JSONからCSVへ変換
CSVに変換できるJSONデータの構造
1. ルート構造が配列([...])であること
2. その配列のメンバーは同一の構造を持つJSONオブジェクトで構成されいること
3. さらにそのJSONオブジェクトの値(VALUE)はプリミティブ型(文字列/数値/Bool/Null)であること
[
{"hoge": 1, "piyo": true, "fuga": "ポヨ田 ムウ夫"},
{"hoge": 4, "piyo": false, "fuga": "ガビ川 ムゲ彦"},
{"hoge": 9, "piyo": false, "fuga": "ペケ山 ムモ子"}
]
👇CSV変換後
hoge,piyo,fuga
1,true,ポヨ田 ムウ夫
4,false,ガビ川 ムゲ彦
9,false,ペケ山 ムモ子
JSONをCSV形式に変換する方法
$ JSON_TEXT=$(cat << EOF
[
{"hoge": 1, "piyo": true, "fuga": "ポヨ田 ムウ夫"},
{"hoge": 4, "piyo": false, "fuga": "ガビ川 ムゲ彦"},
{"hoge": 9, "piyo": false, "fuga": "ペケ山 ムモ子"}
]
EOF
)
$ echo "$JSON_TEXT" | jq -r '
#①JSON配列からCSVへ変換する魔法の呪文
(.[0]|to_entries|map(.key)),(.[]|[.[]])|@csv
' | awk -F"," '
#③数字の3桁区切りを処理するオプション関数
function add_(num) {
count = split(num, arr, ".");
integer = arr[1];
if (count > 1) minority = arr[2];
while (match(integer, /[0-9]+/) > 0) {
if (RLENGTH <= 3) break;
else {
num1 = substr(integer, 1, RSTART+RLENGTH-4);
num2 = substr(integer, RSTART+RLENGTH-3);
integer = num1 "_" num2;
}
}
return count > 1 ? integer "." minority : integer;
}
NR == 1 {
#②「"」の処理
gsub(/"/,"",$0);
print $0;
}
NR > 1 {
for (i = 1; i <= NF; i++) {
if ($i ~ /^[+-]?([0-9]+[.]?[0-9]*|[.][0-9]+)([eE][+-]?[0-9]+)?$/) {
#③3桁以上の数字にアンダースコアを追加(例: 10000 --> 10_000)
printf add_($i);
} else {
#②「"」の処理
gsub(/"/,"",$i);
printf $i;
}
if (i < NF) { printf "," }
}
print ""
}
'
#👇出力
1,true,ポヨ田 ムウ夫
4,false,ガビ川 ムゲ彦
9,false,ペケ山 ムモ子
①
②
-r
「"(ダブルクォーテーション)」
"hoge","piyo","fuga"
1,true,"ポヨ田 ムウ夫"
4,false,"ガビ川 ムゲ彦"
9,false,"ペケ山 ムモ子"
gsub関数
$ echo "$JSON_TEXT" | jq -r '
(.[0]|to_entries|map(.key)),(.[]|[.[]])|@csv
' | sed -r 's/"//g' | ...省略
「_(アンダースコア)」
2934 --> 2_934
42755903478709405 --> 42_755_903_478_709_405
③
③
まとめ
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー