notebook

都内でWEB系エンジニアやってます。

jqでjsonからCSVを生成する

ちょっとした集計などで使うデータの形式はjsonが多いが、たまにツール側の事情でCSVが欲しくなるときもある

具体的に言うと、データポータルにアップロードして単発で使いたい場合はCSVしかサポートされていないのでCSVが必要だったりする

さくっと変換できると何かと楽なのでメモとして残しておく

例として次のようなよくありそうなフォーマットで行ってみる

  • members.json
[
  {
    "name": "hoge",
    "post": "manager",
    "month": "2022-01-01"
  },
  {
    "name": "hoge",
    "post": "manager",
    "month": "2021-12-01"
  },
  {
    "name": "hoge",
    "post": "member",
    "month": "2021-11-01"
  },
  {
    "name": "fuga",
    "post": "member",
    "month": "2022-01-01"
  },
  {
    "name": "fuga",
    "post": "member",
    "month": "2021-12-01"
  },
  {
    "name": "fuga",
    "post": "member",
    "month": "2021-11-01"
  }
]

処理の流れは下記

  • 外側の配列を展開して各行を配列として定義し直す
  • パイプで配列に対して@csvを通す

ヘッダなし

$ cat members.json| jq -r '.[]|[.name,.post,.month]|@csv'
"hoge","manager","2022-01-01"
"hoge","manager","2021-12-01"
"hoge","member","2021-11-01"
"fuga","member","2022-01-01"
"fuga","member","2021-12-01"
"fuga","member","2021-11-01"
  • -r オプション

-rで出力しないと行自体にもクオートが付与されてしまう

 cat members.json| jq '.[]|[.name,.post,.month]|@csv'
"\"hoge\",\"manager\",\"2022-01-01\""
"\"hoge\",\"manager\",\"2021-12-01\""
"\"hoge\",\"member\",\"2021-11-01\""
"\"fuga\",\"member\",\"2022-01-01\""
"\"fuga\",\"member\",\"2021-12-01\""
"\"fuga\",\"member\",\"2021-11-01\""

-r output raw strings, not JSON texts;

JSONテキストではなく生文字列を出力する

CSV以外の使い方だと、jsonの中の特定の値だけ取り出して次のシェルにパイプで渡すパターンが考えられる

ヘッダあり

$ cat members.json| jq -r '["name","post","month"],(.[]|[.name,.post,.month])|@csv'
"name","post","month"
"hoge","manager","2022-01-01"
"hoge","manager","2021-12-01"
"hoge","member","2021-11-01"
"fuga","member","2022-01-01"
"fuga","member","2021-12-01"
"fuga","member","2021-11-01"

ヘッダが欲しい場合はファイルの中身をjqでよしなにする前にヘッダ用の配列を作っておきカンマでつなげる

ヘッダを動的にしたい

扱うカラム数が多い場合、今までの手法だとカラム数分だけヘッダと値を書く必要がある

サンプル程度の数だったら苦にならないが数が多ければ多いほど苦になる

そこで、下記のようにすることで解決できる

$ cat members.json | jq -r '(.[0]|to_entries|map(.key)),(.[]|[.[]])|@csv'
"name","post","month"
"hoge","manager","2022-01-01"
"hoge","manager","2021-12-01"
"hoge","member","2021-11-01"
"fuga","member","2022-01-01"
"fuga","member","2021-12-01"
"fuga","member","2021-11-01"
  • ヘッダ行

(.[0]|to_entries|map(.key))

1行目のキーを取ってきてヘッダ用に整形

最初keys通せばよいだけでは? と思って試してみたが順序が担保できない模様

なのでto_entriesを挟む必要があるみたい

$ cat members.json| jq '.[0]|keys'
[
  "month",
  "name",
  "post"
]
$ cat members.json| jq '.[0]|to_entries|map(.key)'
[
  "name",
  "post",
  "month"
]
  • 2行目以降

(.[]|[.[]])

2行目以降は配列を展開してさらに展開したものを配列で包む

2個目の.[]の挙動を知らなかった、RubyでいうHash.valuesみたいな感じ

$ cat members.json| jq '.[0]'
{
  "name": "hoge",
  "post": "manager",
  "month": "2022-01-01"
}
$ cat members.json| jq '.[0]|.[]'
"hoge"
"manager"
"2022-01-01"
$ cat members.json| jq '.[0]|[.[]]'
[
  "hoge",
  "manager",
  "2022-01-01"
]

感想

CSVを扱うときのメモの予定だったが他にも使えそうな手法を試すことができて勉強になった

参考

json - How to add a header to CSV export in jq? - Stack Overflow

stackoverflow.com