以下3つの配列からなるjsonから、csvを作成(変換)してみます。
sample1.json
[
{
"name": "name1",
"ip":
[
"192.168.0.1/32",
"192.168.0.2/32"
],
"dstname": "dst1"
},
{
"name": "name2",
"ip":
[
"192.168.1.1/32",
"192.168.1.2/32"
],
"dstname": "dst2"
},
{
"name": "name3",
"dstname": "dst3"
}
]
まずは、簡単な所から。
各配列の「.name」と「.dstname」の一覧をCSV化
$ cat sample1.json | jq -r '.[] |
[
.name,
.dstname
] | @csv'
<出力>
"name1","dst1"
"name2","dst2"
"name3","dst3"
.ip の値もcsvに出力してみましょう。.ipは配列なので、複数のIPが記載されている場合はスペース区切りで結合し、IPの記載がない場合は空文字に変換。
$ cat sample1.json | jq -r '.[] |
[
.name,
(if .ip then .ip | join(" ") else "" end),
.dstname
] | @csv'
<出力>
"name1","192.168.0.1/32 192.168.0.2/32","dst1"
"name2","192.168.1.1/32 192.168.1.2/32","dst2"
"name3","","dst3"
次は、jsonの階層が深くなって、最上位の階層にメタ情報が付与されているjsonを考えてみます。
sample2.json
[
{
"rulename": "firstcollection",
"rules":
[
{
"name": "name1",
"ip":
[
"192.168.0.1/32",
"192.168.0.2/32"
],
"dstname": "dst1"
},
{
"name": "name2",
"ip":
[
"192.168.1.1/32",
"192.168.1.2/32"
],
"dstname": "dst2"
},
{
"name": "name3",
"dstname": "dst3"
}
]
}
]
ここでは、.rulename というメタ情報もcsvの1カラムに出力することを目標とします。
出力イメージ
"firstcollection","name1","192.168.0.1/32 192.168.0.2/32","dst1"
"firstcollection","name2","192.168.1.1/32 192.168.1.2/32","dst2"
"firstcollection","name3","","dst3"
案1 .rulenameと.rulesを結合
.rulename と .rules 配下の各配列を結合後、csvに変換します。
まずは結合だけをやってみます。rulename: と rule: は結合後のキーの名前なので任意の名前を指定できますが、今回は元のjsonのキー名をほぼそのまま使っています。
$ cat sample2.json | jq -r '.[] | { rulename: .rulename, rule: .rules[] }'
{
"rulename": "firstcollection",
"rule": {
"name": "name1",
"ip": [
"192.168.0.1/32",
"192.168.0.2/32"
],
"dstname": "dst1"
}
}
{
"rulename": "firstcollection",
"rule": {
"name": "name2",
"ip": [
"192.168.1.1/32",
"192.168.1.2/32"
],
"dstname": "dst2"
}
}
{
"rulename": "firstcollection",
"rule": {
"name": "name3",
"dstname": "dst3"
}
}
.rulename と .rules[] の各配列が結合されました。
余談ですが、結合時に欲張って .rules[].name や .rule[].dstname などを指定すると、それぞれの値が組み合わされて(直積)、意味のない集合になってしまうので注意が必要です。今回は3要素しか結合してないのですぐに処理が終わりましたが、要素が増えると爆発的に組み合わせが増えてCPUを食いつぶします。
$ cat sample2.json | jq -r '.[] | { rulename: .rulename, name: .rules[].name, dstname: .rules[].dstname }'
{
"rulename": "firstcollection",
"name": "name1",
"dstname": "dst1"
}
{
"rulename": "firstcollection",
"name": "name1", ★ name1とdst2という異なる配列の値が結合される
"dstname": "dst2" ★
}
{
"rulename": "firstcollection",
"name": "name1",
"dstname": "dst3"
}
{
"rulename": "firstcollection",
"name": "name2",
"dstname": "dst1"
}
{
"rulename": "firstcollection",
"name": "name2",
"dstname": "dst2"
}
{
"rulename": "firstcollection",
"name": "name2",
"dstname": "dst3"
}
{
"rulename": "firstcollection",
"name": "name3",
"dstname": "dst1"
}
{
"rulename": "firstcollection",
"name": "name3",
"dstname": "dst2"
}
{
"rulename": "firstcollection",
"name": "name3",
"dstname": "dst3"
}
話を元に戻します。結合後のjsonをcsvに変換するのは、最初と同じです。
$ cat sample2.json | jq -r '.[] | { rulename: .rulename, rule: .rules[] } |
[
.rulename, .rule.name,
(if .rule.ip then .rule.ip | join(" ") else "" end),
.rule.dstname
] | @csv'
<出力>
"firstcollection","name1","192.168.0.1/32 192.168.0.2/32","dst1"
"firstcollection","name2","192.168.1.1/32 192.168.1.2/32","dst2"
"firstcollection","name3","","dst3"
案2 .rulenameを変数に入れて、配列化時に埋め込む
.rulenameと.rulesを結合するのではなく、.rules内に.rulenameの値を入れ込む方法でも実現できます。以下は .rulename の値を $rulename という変数に入れ、[ ] 内で再利用しています。
$ cat sample2.json | jq -r '.[] | .rulename as $rulename | .rules[] |
[
$rulename,
.name,
(if .ip then .ip | join(" ") else "" end),
.dstname
] | @csv'
<出力>
"firstcollection","name1","192.168.0.1/32 192.168.0.2/32","dst1"
"firstcollection","name2","192.168.1.1/32 192.168.1.2/32","dst2"
"firstcollection","name3","","dst3"
こちらの方が記述がシンプルですね。
案3 文字列結合を使って、自力でcsv化
.rulename と、.rules[] の各要素を、”+” を使って手動で結合する方法(荒業?)です。@csv を使っていないためcsvの値がダブルクォーテーションで囲まれていないですが、これでも目的は達成できそうです。
$ cat sample2.json | jq -r '.[] |
.rulename + "," +
(
.rules[] |
.name + "," +
(if .ip then .ip | join(" ") else "" end) + "," +
.dstname
)'
<出力>
firstcollection,name1,192.168.0.1/32 192.168.0.2/32,dst1
firstcollection,name2,192.168.1.1/32 192.168.1.2/32,dst2
firstcollection,name3,,dst3