Help us understand the problem. What is going on with this article?

jqコマンドによるjsonデータ操作メモ

まえがき

json形式はファイル送受信で使用される電文フォーマット形式の1つらしい。jsonデータを扱うにはjqコマンドを駆使してハンドリングすることが多い。json形式データに普段業務で触れてもいない(私)、ないしは興味があって触り始めたけど、jqコマンドの使い方を覚えていく必要があって多機能過ぎて覚えるの大変ではかどらないみたい(私)なことがあった。そこで、jsonデータを正規化してデータベースに落とし込んでsql等で弄くり倒したほうが、はかどるのではと思って、そのプロセスをそのまま書いてみることにした。

目指したい形

こうドット記法でのkeyとそれに紐づくvalueのような形式で私はデータを管理したい。ナマのjsonだとkeyの値がどこまでで、一意になるか追いづらい。(追えていないだけ。)

["IPAM.Config.Gateway","172.17.0.1"]
["IPAM.Config.Subnet","172.17.0.0/16"]
["IPAM.Driver","default"]
["IPAM.Options",null]

参考文献

jq Manual (development version)
鬼車のインストール

環境

jq1.6があるみたいなので、それをソースコードからビルドして使うことにした。ただ以下でインストールしたほうが早い。

[sqlite💞a06d1a5911c2 (日 10月 20 17:21:16) ~/script_scratch/jq/jq-1.6]$sudo yum install -y jq
Package jq-1.5-1.el7.x86_64 already installed and latest version

jqソース落としてくる。

[sqlite💜a06d1a5911c2 (日 10月 20 16:32:09) ~/script_scratch/jq]$curl -LO https://github.com/stedolan/jq/releases/download/jq-1.6/jq-1.6.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   602    0   602    0     0   1446      0 --:--:-- --:--:-- --:--:--  1447
100 1709k  100 1709k    0     0   534k      0  0:00:03  0:00:03 --:--:--  712k
[sqlite💜a06d1a5911c2 (日 10月 20 16:38:46) ~/script_scratch/jq]$ll
total 1728
-rw-r--r-- 1 root   root        56 10月 19 16:41 a.json
-rw-r--r-- 1 root   root       171 10月 19 16:41 b.json
-rw-rw-r-- 1 sqlite sqlite 1750584 10月 20 16:37 jq-1.6.tar.gz
-rw-r--r-- 1 root   root       529 10月 19 16:50 normalize_json.sh
-rw-r--r-- 1 root   root       369 10月 19 16:47 unify_type.sh
[sqlite💜a06d1a5911c2 (日 10月 20 16:39:54) ~/script_scratch/jq]$tar xvf jq-1.6.tar.gz 
[sqlite💜a06d1a5911c2 (日 10月 20 16:40:07) ~/script_scratch/jq]$cd jq-1.6
[sqlite💜a06d1a5911c2 (日 10月 20 16:42:12) ~/script_scratch/jq/jq-1.6]./configure
configure: Oniguruma was not found. Will use the packaged oniguruma.
checking that generated files are newer than configure... done

onigurumaが見つからないとあるので検索した。これはsudo yum install -y jqで依存性の解決で取り込まれたもの。

[sqlite💜a06d1a5911c2 (日 10月 20 16:48:02) ~/script_scratch/jq/jq-1.6]$find / -name "*oniguruma.h*" 2>/dev/null
/home/sqlite/script_scratch/jq/jq-1.6/modules/oniguruma/src/oniguruma.h

最新探したら、最近更新があったらしいので、そちらを使用することにした。鬼車のインストール

[sqlite💜a06d1a5911c2 (日 10月 20 16:50:55) ~/script_scratch/jq]$git clone https://github.com/kkos/oniguruma.git
[sqlite💜a06d1a5911c2 (日 10月 20 16:51:09) ~/script_scratch/jq]$cd oniguruma/

configureスクリプトが存在しなかったので、実行した。

[sqlite💜a06d1a5911c2 (日 10月 20 16:56:05) ~/script_scratch/jq/oniguruma]$autoreconf -vfi
src/Makefile.am:19: error: Libtool library used but 'LIBTOOL' is undefined

エラーでたので、これを元に解決を試みる。error: Libtool library used but 'LIBTOOL' is undefined

[sqlite💜a06d1a5911c2 (日 10月 20 17:00:53) ~/script_scratch/jq/oniguruma]$sudo yum install -y libtool
Installed:
  libtool.x86_64 0:2.4.2-22.el7_3                                                                                                                                                                                  

Complete!

再度実行。先ほどのメッセージはでなくなった。configureスクリプトも作成されていた。

[sqlite💜a06d1a5911c2 (日 10月 20 17:00:53) ~/script_scratch/jq/oniguruma]$autoreconf -vfi
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
autoreconf: configure.ac: tracing
autoreconf: running: libtoolize --copy --force
libtoolize: putting auxiliary files in `.'.
libtoolize: copying file `./ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'
autoreconf: running: /usr/bin/autoconf --force
autoreconf: running: /usr/bin/autoheader --force
autoreconf: running: automake --add-missing --copy --force-missing
autoreconf: Leaving directory `.'
[sqlite💜a06d1a5911c2 (日 10月 20 17:01:35) ~/script_scratch/jq/oniguruma]$ls
AUTHORS         ChangeLog  Makefile.am  README           aclocal.m4      cmake         config.sub    depcomp    index.html     ltmain.sh     make_win32.bat  onig-config.in         sample  test-driver
CMakeLists.txt  HISTORY    Makefile.in  README.md        autogen.sh      compile       configure     doc        index_ja.html  m4            make_win64.bat  oniguruma.pc.cmake.in  src     windows
COPYING         INSTALL    NEWS         README_japanese  autom4te.cache  config.guess  configure.ac  harnesses  install-sh     make_win.bat  missing         oniguruma.pc.in        test

configure&&make&&make install実行

[sqlite💜a06d1a5911c2 (日 10月 20 17:03:05) ~/script_scratch/jq/oniguruma]$./configure --prefix=/usr/local --enable-shared
[sqlite💜a06d1a5911c2 (日 10月 20 17:05:34) ~/script_scratch/jq/oniguruma]$make
[sqlite💜a06d1a5911c2 (日 10月 20 17:06:01) ~/script_scratch/jq/oniguruma]$sudo make install

ヘッダファイル確認

[sqlite💜a06d1a5911c2 (日 10月 20 17:06:32) ~/script_scratch/jq/oniguruma]$find / -name "*oniguruma*" 2>/dev/null
/var/lib/yum/yumdb/o/15929399271ae1a72850ab5adcb07fbd4b716ced-oniguruma-5.9.5-3.el7-x86_64
/usr/local/lib/pkgconfig/oniguruma.pc
/usr/local/include/oniguruma.h
/home/sqlite/script_scratch/jq/oniguruma
/home/sqlite/script_scratch/jq/oniguruma/oniguruma.pc
/home/sqlite/script_scratch/jq/oniguruma/oniguruma.pc.cmake.in
/home/sqlite/script_scratch/jq/oniguruma/src/oniguruma.h
/home/sqlite/script_scratch/jq/oniguruma/oniguruma.pc.in
/home/sqlite/script_scratch/jq/jq-1.6/modules/oniguruma
/home/sqlite/script_scratch/jq/jq-1.6/modules/oniguruma/oniguruma.pc.cmake.in
/home/sqlite/script_scratch/jq/jq-1.6/modules/oniguruma/src/oniguruma.h
/home/sqlite/script_scratch/jq/jq-1.6/modules/oniguruma/oniguruma.pc.in
/home/sqlite/script_scratch/jq/jq-1.6/sig/v1.5/jq-linux32-no-oniguruma.asc

jqのインストールの再開。oniguruma認識できているぽい。

[sqlite💜a06d1a5911c2 (日 10月 20 17:10:45) ~/script_scratch/jq/jq-1.6]$./configure
checking oniguruma.h usability... yes
checking oniguruma.h presence... yes
checking for oniguruma.h... yes
checking for onig_version in -lonig... yes
[sqlite💜a06d1a5911c2 (日 10月 20 17:15:54) ~/script_scratch/jq/jq-1.6]$make
[sqlite💜a06d1a5911c2 (日 10月 20 17:15:54) ~/script_scratch/jq/jq-1.6]$sudo make install

alias貼ってjq1.6を使えるようにした。

[sqlite💜a06d1a5911c2 (日 10月 20 17:20:44) ~/script_scratch/jq/jq-1.6]$echo "alias jq=/usr/local/bin/jq" >>~/.bashrc
[sqlite💜a06d1a5911c2 (日 10月 20 17:21:06) ~/script_scratch/jq/jq-1.6]$source ~/.bashrc
[sqlite💞a06d1a5911c2 (日 10月 20 17:21:12) ~/script_scratch/jq/jq-1.6]$jq --version
jq-1.6

jqでサポートしている型とか関数とかプロパティとかに触れてみる

マニュアルによるとnumbers, strings, booleans, arrays, objects及びnullがサポート対象のようだ。配列は[]で内包されたものを表現し、オブジェクトは{}で内包されたものを表現しているようだ。

[sqlite💞a06d1a5911c2 (日 10月 20 17:39:28) ~/script_scratch/jq]$cat a.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 17:39:35) ~/script_scratch/jq]$cat a.json | jq '.|type'
"object"

to_entries関数{}で内包されたjsonオブジェクトをkey:value形式のオブジェクトに正規化して配列[]で内包する形式に変換してくれるらしい。

[sqlite💞a06d1a5911c2 (日 10月 20 17:40:14) ~/script_scratch/jq]$cat a.json | jq '.|to_entries'
[
  {
    "key": "Labels",
    "value": {}
  },
  {
    "key": "Containers",
    "value": {}
  },
  {
    "key": "Options",
    "value": {}
  }
]
[sqlite💞a06d1a5911c2 (日 10月 20 17:40:59) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|type'
"array"
[sqlite💞a06d1a5911c2 (月 10月 21 00:26:49) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:26:54) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key'
"Labels"
"Containers"
"Options"

内包しているラップを剥がすには.[]を使うようだ。

[sqlite💞a06d1a5911c2 (日 10月 20 17:45:56) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 17:46:04) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|type'
"object"
"object"
"object"

keyとvalueに紐づく値を取得するには.key,.valueでアクセスする。

[sqlite💞a06d1a5911c2 (日 10月 20 17:47:37) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key'
"Labels"
"Containers"
"Options"
[sqlite💞a06d1a5911c2 (日 10月 20 17:48:51) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value'
{}
{}
{}
[sqlite💞a06d1a5911c2 (日 10月 20 17:48:59) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key|type'
"string"
"string"
"string"
[sqlite💞a06d1a5911c2 (日 10月 20 17:51:30) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|type'
"object"
"object"
"object"

空のオブジェクトを判別するにはlength関数が良さそうだ。また、自分でオブジェクトのデータ形式を作り出すことも配列コンストラクタ[]、オブジェクトコンストラクタ{}で可能らしい。

[sqlite💞a06d1a5911c2 (日 10月 20 17:58:43) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|.'
{}
{}
{}
[sqlite💞a06d1a5911c2 (日 10月 20 17:58:45) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|.|type'
"object"
"object"
"object"
[sqlite💞a06d1a5911c2 (日 10月 20 17:58:51) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then "empty" else . end'
"empty"
"empty"
"empty"
[sqlite💞a06d1a5911c2 (日 10月 20 17:58:59) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then "empty" else . end|type'
"string"
"string"
"string"
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:02) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then {"empty"} else . end'
{
  "empty": null
}
{
  "empty": null
}
{
  "empty": null
}
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:12) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then {"empty"} else . end|type'
"object"
"object"
"object"
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:19) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then ["empty"] else . end'
[
  "empty"
]
[
  "empty"
]
[
  "empty"
]
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:33) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then ["empty"] else . end|type'
"array"
"array"
"array"
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:39) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then {key:null,value:"empty"} else . end'
{
  "key": null,
  "value": "empty"
}
{
  "key": null,
  "value": "empty"
}
{
  "key": null,
  "value": "empty"
}
[sqlite💞a06d1a5911c2 (日 10月 20 17:59:59) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|if length==0 then {key:null,value:"empty"} else . end|type'
"object"
"object"
"object"

else分岐の方は以下のようになる

[sqlite💞a06d1a5911c2 (日 10月 20 18:06:42) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key|if length==0 then {key:null,value:"empty"} else . end'
"Labels"
"Containers"
"Options"
[sqlite💞a06d1a5911c2 (日 10月 20 18:06:49) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key|if length==0 then {key:null,value:"empty"} else . end | type'
"string"
"string"
"string"

再帰処理した結果を蓄積してくれるreduce関数もあるようだ。これは便利そう。要素に格納された順に指定した演算処理を再帰的に実行してくれるようだ。10+2+5+3=20。初期値を0以外にして演算子をマイナスとかにするとわかりやすい。reduce関数で処理する引数を事前にラップを剥がして渡すか(.で与えるか)、関数内でラップを剥がすか(.[]で与えるか)を意識するのは大切だと感じた。.で与えると各レコード単位で処理して、.[]で与えるとレコードを集約した値を返却してくれる。. as $itemの部分は再帰対象のリストアップをしていて、()の中には再帰対象のリストに対する処理内容(式)を記載するイメージ。.(ドット)は現在までの蓄積結果を保持していて、それに対してリストアップしたアイテムから一つずつフェッチして指定した演算処理を実施している。0;は再帰処理の非再帰項の部分で、最初の1回しか実行されない。初期値の宣言をしている。まとめると、reduce式の()内は再帰処理の1回目と1回目以外を定義しているようだ。

関数内でラップを剥がす場合

[sqlite💞a06d1a5911c2 (日 10月 20 18:16:25) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.'
[
  10,
  2,
  5,
  3
[sqlite💞a06d1a5911c2 (日 10月 20 19:58:58) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|type'
"array"
[sqlite💞a06d1a5911c2 (日 10月 20 20:00:34) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|reduce .[] as $item(0;$item)'
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:04:26) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|reduce .[] as $item(0;.+$item)'
20

事前にラップを剥がして関数に渡す場合

[sqlite💞a06d1a5911c2 (日 10月 20 19:59:17) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:00:32) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|type'
"number"
"number"
"number"
"number"
[sqlite💞a06d1a5911c2 (日 10月 20 20:04:33) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|reduce . as $item(0;$item)'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:06:48) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|reduce . as $item(0;.+$item)'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:06:51) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|reduce . as $item(10;.+$item)'
20
12
15
13

文字列も漸化的に蓄積していく様子を表現したかったけど、うまくいかなかった。

関数内でラップを剥がす場合

[sqlite💞a06d1a5911c2 (日 10月 20 20:11:41) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.'
[
  "10",
  "2",
  "5",
  "3"
]
[sqlite💞a06d1a5911c2 (日 10月 20 20:11:47) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|type'
"array"
[sqlite💞a06d1a5911c2 (日 10月 20 20:13:08) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|reduce .[] as $item("";$item)'
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:13:56) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|reduce .[] as $item("";.+$item)'
"10253"
[sqlite💞a06d1a5911c2 (日 10月 20 20:14:01) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|reduce .[] as $item("";.+","+$item)'
",10,2,5,3"

事前にラップを剥がして関数に渡す場合

[sqlite💞a06d1a5911c2 (日 10月 20 20:14:15) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:14:53) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|type'
"string"
"string"
"string"
"string"
[sqlite💞a06d1a5911c2 (日 10月 20 20:15:04) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|reduce . as $item("";$item)'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:15:29) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|reduce . as $item("";.+$item)'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:15:34) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|reduce . as $item("";.+","+$item)'
",10"
",2"
",5"
",3"

よく探したら、foreach制御構文もあるらしい。これでやってみる。これもreduceと同じで引数に与える形式を意識する必要がありそう。

関数内でラップを剥がす場合

リニアに足しこまれている。

[sqlite💞a06d1a5911c2 (日 10月 20 20:15:41) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.'
[
  10,
  2,
  5,
  3
]
[sqlite💞a06d1a5911c2 (日 10月 20 20:18:11) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|type'
"array"
[sqlite💞a06d1a5911c2 (日 10月 20 20:18:21) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|foreach .[] as $item(0;$item)'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:18:42) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|foreach .[] as $item(0;.+$item)'
10
12
17
20
[sqlite💞a06d1a5911c2 (日 10月 20 21:02:56) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|foreach .[] as $item(10;.+$item)'
20
22
27
30

事前にラップを剥がして関数に渡す場合

前回の蓄積結果を意識せず、各明細行単位で初期値との足し算を行っている。

[sqlite💞a06d1a5911c2 (日 10月 20 20:18:47) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.'
[
  10,
  2,
  5,
  3
]
[sqlite💞a06d1a5911c2 (日 10月 20 20:20:32) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:20:34) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|type'
"number"
"number"
"number"
"number"
[sqlite💞a06d1a5911c2 (日 10月 20 20:20:38) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|foreach . as $item(0;$item)'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:20:54) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|foreach . as $item(0;.+$item)'
10
2
5
3
[sqlite💞a06d1a5911c2 (日 10月 20 20:20:59) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.[]|foreach . as $item(10;.+$item)'
20
12
15
13

文字列の場合でもやってみる。

関数内でラップを剥がす場合

文字列がカンマ区切りをセンパレータにしてリニアに足しこまれている。

[sqlite💞a06d1a5911c2 (日 10月 20 20:21:16) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.'
[
  "10",
  "2",
  "5",
  "3"
]
[sqlite💞a06d1a5911c2 (日 10月 20 20:24:35) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|type'
"array"
[sqlite💞a06d1a5911c2 (日 10月 20 20:24:38) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|foreach .[] as $item("";$item)'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:25:00) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|foreach .[] as $item("";.+$item)'
"10"
"102"
"1025"
"10253"
[sqlite💞a06d1a5911c2 (日 10月 20 20:25:03) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|foreach .[] as $item("";.+","+$item)'
",10"
",10,2"
",10,2,5"
",10,2,5,3"

事前にラップを剥がして関数に渡す場合

前回の蓄積結果を意識せず、各明細行単位で初期値との足し算を行っている。

[sqlite💞a06d1a5911c2 (日 10月 20 20:25:09) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.'
[
  "10",
  "2",
  "5",
  "3"
]
[sqlite💞a06d1a5911c2 (日 10月 20 20:25:39) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:25:41) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|type'
"string"
"string"
"string"
"string"
[sqlite💞a06d1a5911c2 (日 10月 20 20:25:47) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|foreach . as $item("";$item)'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:26:04) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|foreach . as $item("";.+$item)'
"10"
"2"
"5"
"3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:26:08) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.[]|foreach . as $item("";.+","+$item)'
",10"
",2"
",5"
",3"

数値演算と文字列演算での比較をするとわかりやすいか

[sqlite💞a06d1a5911c2 (日 10月 20 20:26:15) ~/script_scratch/jq]$echo '["10","2","5","3"]' | jq '.|foreach .[] as $item("";.+","+$item)'
",10"
",10,2"
",10,2,5"
",10,2,5,3"
[sqlite💞a06d1a5911c2 (日 10月 20 20:27:22) ~/script_scratch/jq]$echo "[10,2,5,3]" | jq '.|foreach .[] as $item(0;.+$item)'
10
12
17
20

key:value形式のデータで遊んで試してみる

[sqlite💞a06d1a5911c2 (日 10月 20 21:22:39) ~/script_scratch/jq]$cat a.json | jq '.|to_entries'
[
  {
    "key": "Labels",
    "value": {}
  },
  {
    "key": "Containers",
    "value": {}
  },
  {
    "key": "Options",
    "value": {}
  }
]
[sqlite💞a06d1a5911c2 (日 10月 20 21:22:42) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item({};$item)'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 21:30:45) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item({};$item)'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 21:31:01) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item({};$item)|type'
"object"
"object"
"object"
[sqlite💞a06d1a5911c2 (日 10月 20 21:31:08) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item("";.+$item.key)'
"Labels"
"LabelsContainers"
"LabelsContainersOptions"
[sqlite💞a06d1a5911c2 (日 10月 20 21:31:26) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item("";.+","+$item.key)'
",Labels"
",Labels,Containers"
",Labels,Containers,Options"
[sqlite💞a06d1a5911c2 (日 10月 20 21:31:32) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|foreach .[] as $item("";.+","+$item.key)|type'
"string"
"string"
"string"

reduce関数とforeach関数の関係を表にしてみる

蓄積結果をサマリ行単位で取得したい場合はパターン1で使って、蓄積結果を明細行単位で漸化的に取得したい場合はパターン3で取得すればいいのかな。パターン2と4は同じかな。

image.png

jsonデータの全要素をオブジェクト型に統一

配列の中にjsonオブジェクトが入り込んでくるケースを考えると、受け取った引数をすべてオブジェクト型に統一しておきたいところ。いろいろ試した。

まず、keys[]プロパティを使ってみた。

これはkeyに紐づくvalue値がオブジェクト型か配列型か問わず、keyすべてを取得できるように実装されているようだ。.keyと比較してみる。

同一階層レベルに複数のkey:valueが存在している場合

[sqlite💞a06d1a5911c2 (日 10月 20 21:58:40) ~/script_scratch/jq]$cat a.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:02:13) ~/script_scratch/jq]$cat a.json | jq '.|keys[]'
"Containers"
"Labels"
"Options"
[sqlite💞a06d1a5911c2 (月 10月 21 00:01:37) ~/script_scratch/jq]$cat a.json | jq '.key'
null

これは以下の処理をkeys[]メソッドで取得できるように実装しているように思われる。

[sqlite💞a06d1a5911c2 (日 10月 20 22:02:17) ~/script_scratch/jq]$cat a.json | jq '.|to_entries'
[
  {
    "key": "Labels",
    "value": {}
  },
  {
    "key": "Containers",
    "value": {}
  },
  {
    "key": "Options",
    "value": {}
  }
]
[sqlite💞a06d1a5911c2 (日 10月 20 22:02:47) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:02:58) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.key'
"Labels"
"Containers"
"Options"

同一階層レベルに単一のkey:valueが存在している場合

[sqlite💞a06d1a5911c2 (日 10月 20 22:04:45) ~/script_scratch/jq]$cat b.json | jq '.'
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:04:48) ~/script_scratch/jq]$cat b.json | jq '.|keys[]'
"IPAM"
[sqlite💞a06d1a5911c2 (月 10月 21 00:12:55) ~/script_scratch/jq]$cat b.json | jq '.key'
null
[sqlite💞a06d1a5911c2 (日 10月 20 22:05:01) ~/script_scratch/jq]$cat c.json | jq '.'
{
  "Config": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:06:03) ~/script_scratch/jq]$cat c.json | jq '.|keys[]'
"Config"

keys[] as \$item\$itemに格納しておいた値で:(コロン)の左側をkeyにするとき,($item)でいけた。カッコが必要。コマンド置換みたいなものであろう。

[sqlite💞a06d1a5911c2 (日 10月 20 22:47:10) ~/script_scratch/jq]$cat d.json | jq '.'
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:48:23) ~/script_scratch/jq]$cat d.json | jq '.["IPAM"]'
{
  "Driver": "default",
  "Options": null
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:48:30) ~/script_scratch/jq]$cat d.json | jq '(.["IPAM"])'
{
  "Driver": "default",
  "Options": null
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:48:38) ~/script_scratch/jq]$cat d.json | jq '{"IPAM":.["IPAM"]}'
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:48:59) ~/script_scratch/jq]$cat d.json | jq '{"IPAM":(.["IPAM"])}'
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}

IPAMの部分を動的に。

[sqlite💞a06d1a5911c2 (日 10月 20 22:49:06) ~/script_scratch/jq]$cat d.json | jq 'reduce keys[] as $item({};$item)'
"IPAM"
[sqlite💞a06d1a5911c2 (日 10月 20 22:50:36) ~/script_scratch/jq]$cat d.json | jq 'reduce keys[] as $item({};{($item):$item})'
{
  "IPAM": "IPAM"
}
[sqlite💞a06d1a5911c2 (日 10月 20 22:50:50) ~/script_scratch/jq]$cat d.json | jq 'reduce keys[] as $item({};{($item):(.[$item])})'
{
  "IPAM": null
}

コマンド置換されていない!!!

.(ドット)の部分は常に最初の引数に与えた状態のものを与える必要があることに気づいた。持ち回す必要があるので、直前で処理したjsonオブジェクトではなく、実引数に受け取った値を参照する必要があった。

そこで、最初の引数を受け取って何も処理をせず、最初の引数を返却する関数が生まれた。

型はこんなイメージだろう。

jq 'def 関数名(仮引数):\
実引数 as $変数名\
変数名を参照した何らかの処理;\
関数名(実引数)'
cat d.json | jq 'def normalize_json(init_param_json):
 init_param__json as $in
|reduce keys[] as $item({};.+{($item):($in[$item])});
normalize_json(.)'

なにもしていない!

[sqlite💞a06d1a5911c2 (日 10月 20 23:10:51) ~/script_scratch/jq]$cat d.json | jq '.'
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}
[sqlite💞a06d1a5911c2 (日 10月 20 23:10:58) ~/script_scratch/jq]$cat d.json | jq 'def normalize_json(init_param_json):
 init_param__json as $in
|reduce keys[] as $item({};.+{($item):($in[$item])});
normalize_json(.)'
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}

配列の中にjsonオブジェクトが入り込んでくるケースを考えると、受け取った引数をすべてオブジェクト型に統一しておきたいところ。

[sqlite💞a06d1a5911c2 (日 10月 20 23:23:49) ~/script_scratch/jq]$cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))})
else . end;
normalize_json(.)'

やっていることをコメントにかいた。

cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
#型が配列の場合、ラップ剥がして再帰呼び出し
elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))})
#型がオブジェクトの場合、key($itemのこと)を取得してそのkey($itemのこと)に紐づくvalueを取得してそのvalueに紐づく値を引数に渡し再帰呼び出し
else . end;
#上記の型のいずれでもない場合、そのまま出力
normalize_json(.)'

型がオブジェクトの場合、.+をつけていないと以下の出力結果が得られるので、Configに紐づく値をすべて連結しておく必要がある。Configに紐づく値は単一でも複数でもすべて取得しておく。なので、.+をつけている。

[sqlite💞a06d1a5911c2 (日 10月 20 23:30:12) ~/script_scratch/jq]$cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
elif type=="object" then reduce keys[] as $item ({};{($item):($in[$item]|normalize_json(.))})
else . end;
normalize_json(.)'
{
  "Config": {
    "Subnet": "172.17.0.0/16"
  }
}

なお、型がオブジェクトの場合、(\$in[$item]|normalize_json(.))ではなく(\$in[$item])にしてしまうとkeyに紐づく値がオブジェクトの場合でないとき、配列のままになってしまうので、再帰呼び出しをしている。再帰呼び出しまで行った結果をコマンド置換している。

[sqlite💞a06d1a5911c2 (日 10月 20 23:30:17) ~/script_scratch/jq]$cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
elif type=="object" then reduce keys[] as $item ({};{($item):($in[$item])})
else . end;
normalize_json(.)'
{
  "Config": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}

あと、配列か配列以外で分類すると以下のようになる。これは、Subnetに紐づく値("172.17.0.1")を元に再帰呼び出しを行い、型が配列でもないので、keys[]のところで、no keysと言われている。

[sqlite💞a06d1a5911c2 (日 10月 20 23:40:42) ~/script_scratch/jq]$cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
else reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))}) end;
normalize_json(.)'
jq: error (at <stdin>:8): string ("172.17.0.1") has no keys

もうすこし、簡単なjsonファイルを用意してみると、こんな感じ。

[sqlite💞a06d1a5911c2 (日 10月 20 23:44:57) ~/script_scratch/jq]$cat e.json | jq .
[
  {
    "Subnet": "172.17.0.0/16",
    "Gateway": "172.17.0.1"
  }
]
[sqlite💞a06d1a5911c2 (日 10月 20 23:44:59) ~/script_scratch/jq]$cat c.json | jq '
def normalize_json(init_param__json):
init_param__json as $in
|if type=="array" then .[]|normalize_json(.)
else reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))}) end;
normalize_json(.)'
jq: error (at <stdin>:8): string ("172.17.0.1") has no keys

全要素オブジェクト型化ファンクションの完成

以上から、else句がオブジェクト型でない場合に必要であることを踏まえて、以下のようなファンクションに落とし込めると思う。たぶん役に立つであろう。頑張ってつくったけど仕事で使わないけど。😂

[sqlite💞a06d1a5911c2 (日 10月 20 23:57:51) ~/script_scratch/jq]$cat unify_type.sh
#!/bin/bash 
unify_type(){
  jq '
    def normalize_json(init_param__json):
    init_param__json as $in
    |if type=="array" then .[]|normalize_json(.)
    elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))})
    else . end;
    normalize_json(.)
  ' "$@"
}

unify_type "$@"

動作確認

[sqlite💞a06d1a5911c2 (日 10月 20 23:59:09) ~/script_scratch/jq]$cat a.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (日 10月 20 23:59:42) ~/script_scratch/jq]$./unify_type.sh a.json
{
  "Containers": {},
  "Labels": {},
  "Options": {}
}

配列なくっている!

[sqlite💞a06d1a5911c2 (日 10月 20 23:59:45) ~/script_scratch/jq]$cat b.json
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:00:05) ~/script_scratch/jq]$./unify_type.sh b.json
{
  "IPAM": {
    "Config": {
      "Gateway": "172.17.0.1",
      "Subnet": "172.17.0.0/16"
    },
    "Driver": "default",
    "Options": null
  }
}

配列なくなっている!

[sqlite💞a06d1a5911c2 (月 10月 21 00:00:09) ~/script_scratch/jq]$cat c.json
{
  "Config": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:00:39) ~/script_scratch/jq]$./unify_type.sh c.json
{
  "Config": {
    "Gateway": "172.17.0.1",
    "Subnet": "172.17.0.0/16"
  }
}

影響なし!

[sqlite💞a06d1a5911c2 (月 10月 21 00:00:42) ~/script_scratch/jq]$cat d.json
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:01:06) ~/script_scratch/jq]$./unify_type.sh d.json
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}

配列がなくなっている!

[sqlite💞a06d1a5911c2 (月 10月 21 00:01:08) ~/script_scratch/jq]$cat e.json
[
  {
    "Subnet": "172.17.0.0/16",
    "Gateway": "172.17.0.1"
  }
]
[sqlite💞a06d1a5911c2 (月 10月 21 00:01:35) ~/script_scratch/jq]$./unify_type.sh e.json
{
  "Gateway": "172.17.0.1",
  "Subnet": "172.17.0.0/16"
}

ドット記法変換ファンクションの作成

"$@"はto_entries関数の引数に渡している。

[sqlite💞a06d1a5911c2 (月 10月 21 00:48:12) ~/script_scratch/jq]$cat normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
  ;
  normalize_json("")
  ' "$@"
}

normalize_json "$@"

動作確認

[sqlite💞a06d1a5911c2 (月 10月 21 00:48:00) ~/script_scratch/jq]$cat a.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:48:11) ~/script_scratch/jq]$./normalize_json.sh a.json
[
  {
    "key": "Labels",
    "value": {}
  },
  {
    "key": "Containers",
    "value": {}
  },
  {
    "key": "Options",
    "value": {}
  }
]

そのあと、.[]でラップ剥がす。

[sqlite💞a06d1a5911c2 (月 10月 21 00:50:26) ~/script_scratch/jq]$cat ./normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |.[]
  ;
  normalize_json("")
  ' "$@"
}

normalize_json "$@"

前回分のkeyを保持。|.の部分は必要。エラー回避のため。標準入力をそのまま渡すcat -的な役割。

[sqlite💞a06d1a5911c2 (月 10月 21 00:52:49) ~/script_scratch/jq]$cat normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |.[]
    |.key as $prekey
    |.
  ;
  normalize_json("")
  ' "$@"
}
normalize_json "$@"

動作確認

[sqlite💞a06d1a5911c2 (月 10月 21 00:49:36) ~/script_scratch/jq]$cat a.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (月 10月 21 00:50:24) ~/script_scratch/jq]$./normalize_json.sh a.json
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}

次が難しい。これを参考にjqで階層構造を持つオブジェクトをCSVにマップする

オブジェクト型でなかったらそのまま出力。オブジェクト型なら、紐づくkeyの値をすべてドットつなぎにしたいので、再帰呼び出し。そのため、再帰呼び出しの際に渡した引数keyと今回の取得したkeyをコマンド置換してオブジェクトを生み出す処理を追加している。その結果を元に前回のkey値を取得するようにしている。

[sqlite💞a06d1a5911c2 (月 10月 21 01:06:01) ~/script_scratch/jq]$cat ./normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json("")
  ' "$@"
}
normalize_json "$@"

動作確認

[sqlite💞a06d1a5911c2 (月 10月 21 01:40:02) ~/script_scratch/jq]$./normalize_json.sh b.json
{
  "key": "IPAM.Driver",
  "value": "default"
}
{
  "key": "IPAM.Options",
  "value": null
}
{
  "key": "IPAM.Config",
  "value": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}

ただし、このままだと、オブジェクトのvalueがないと何も出力されなくなってしまう。

{key:($kk+.key),value:.value}の部分の.valueが原因と思われる。

[sqlite💞a06d1a5911c2 (月 10月 21 01:06:50) ~/script_scratch/jq]$cat a.json 
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
[sqlite💞a06d1a5911c2 (月 10月 21 01:07:39) ~/script_scratch/jq]$./normalize_json.sh a.json
[sqlite💞a06d1a5911c2 (月 10月 21 01:19:03) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]'
{
  "key": "Labels",
  "value": {}
}
{
  "key": "Containers",
  "value": {}
}
{
  "key": "Options",
  "value": {}
}
[sqlite💞a06d1a5911c2 (月 10月 21 01:19:20) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value'
{}
{}
{}
[sqlite💞a06d1a5911c2 (月 10月 21 01:19:26) ~/script_scratch/jq]$cat a.json | jq '.|to_entries|.[]|.value|length'
0
0
0

そこで、ダミー配列をlengthがゼロの場合に追加して.keyないしは.valueでnullが取得できるようにして文字列演算に影響がないように工夫してみた。

[sqlite💞a06d1a5911c2 (月 10月 21 01:24:04) ~/script_scratch/jq]$cat ./normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |if length==0 then [{key:null,value:null}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json("")
  ' "$@"
}
normalize_json "$@"

動作確認

配列が入るとどうしてもやっぱ鬱陶しい。そこで、先ほど作成しておいた型統一ファンクションでオブジェクト型に統一してから実行することにしてみた。

[sqlite💞a06d1a5911c2 (月 10月 21 01:41:21) ~/script_scratch/jq]$./normalize_json.sh a.json
{
  "key": "Labels.",
  "value": null
}
{
  "key": "Containers.",
  "value": null
}
{
  "key": "Options.",
  "value": null
}
[sqlite💞a06d1a5911c2 (月 10月 21 01:41:22) ~/script_scratch/jq]$./normalize_json.sh b.json
{
  "key": "IPAM.Driver",
  "value": "default"
}
{
  "key": "IPAM.Options",
  "value": null
}
{
  "key": "IPAM.Config",
  "value": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}

こんなかんじ

[sqlite💞a06d1a5911c2 (月 10月 21 01:41:27) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh a.json)
{
  "key": "Containers.",
  "value": null
}
{
  "key": "Labels.",
  "value": null
}
{
  "key": "Options.",
  "value": null
}
[sqlite💞a06d1a5911c2 (月 10月 21 01:41:55) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh b.json)
{
  "key": "IPAM.Config.Gateway",
  "value": "172.17.0.1"
}
{
  "key": "IPAM.Config.Subnet",
  "value": "172.17.0.0/16"
}
{
  "key": "IPAM.Driver",
  "value": "default"
}
{
  "key": "IPAM.Options",
  "value": null
}

それと、normalize_json("")の最初の引数に空文字列を指定しているのは、文字列演算をするため。
normalize_json(.)ではだめ。

[sqlite💞a06d1a5911c2 (月 10月 21 01:30:10) ~/script_scratch/jq]$cat ./normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |if length==0 then [{key:null,value:null}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json(.)
  ' "$@"
}
normalize_json "$@"

[sqlite💞a06d1a5911c2 (月 10月 21 01:30:08) ~/script_scratch/jq]$./normalize_json.sh a.json
jq: error (at a.json:5): object ({"Labels":{...) and string ("Labels") cannot be added

最後に欲しい形式で出力しておしまい。-cオプションは単一行出力。

[sqlite💞a06d1a5911c2 (月 10月 21 01:31:23) ~/script_scratch/jq]$cat ./normalize_json.sh
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq -c '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |if length==0 then [{key:null,value:null}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json("")|[.key,.value]
  ' "$@"
}

normalize_json "$@"

動作確認

オブジェクトの要素単一しかないときドットついてしまうのは、sedとかで適当に処理すればええじゃろ。

[sqlite💞a06d1a5911c2 (月 10月 21 01:43:05) ~/script_scratch/jq]$cat a.json 
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}

[sqlite💞a06d1a5911c2 (月 10月 21 01:42:37) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh a.json)
["Containers.",null]
["Labels.",null]
["Options.",null]

a.json以外はいい感じ。

[sqlite💞a06d1a5911c2 (月 10月 21 01:43:32) ~/script_scratch/jq]$cat b.json 
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}

[sqlite💞a06d1a5911c2 (月 10月 21 01:42:40) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh b.json)
["IPAM.Config.Gateway","172.17.0.1"]
["IPAM.Config.Subnet","172.17.0.0/16"]
["IPAM.Driver","default"]
["IPAM.Options",null]
[sqlite💞a06d1a5911c2 (月 10月 21 01:44:49) ~/script_scratch/jq]$cat c.json 
{
  "Config": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}

[sqlite💞a06d1a5911c2 (月 10月 21 01:42:43) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh c.json)
["Config.Gateway","172.17.0.1"]
["Config.Subnet","172.17.0.0/16"]

[sqlite💞a06d1a5911c2 (月 10月 21 01:45:45) ~/script_scratch/jq]$cat d.json 
{
  "IPAM": {
    "Driver": "default",
    "Options": null
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 01:42:48) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh d.json)
["IPAM.Driver","default"]
["IPAM.Options",null]

[sqlite💞a06d1a5911c2 (月 10月 21 01:46:14) ~/script_scratch/jq]$cat e.json 
[
  {
    "Subnet": "172.17.0.0/16",
    "Gateway": "172.17.0.1"
  }
]
[sqlite💞a06d1a5911c2 (月 10月 21 01:42:53) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh e.json)
["Gateway","172.17.0.1"]
["Subnet","172.17.0.0/16"]

データ投入用に正規化

後日。。末尾どっとけして、ドッドをアンスコとかにして[]外しておけばええじゃろ。


DBぶちこみ

sqlite3ないしpostgresないしoracleなどのDBに。

後日。。

あとがき

DBぶちこみまで、チョー時間かかってしまってしかもできていないですが、それは後日。jsonデータ形式が登場してきたことで、いろいろデータのハンドリングが多様化してきて、覚えなくては行けないことが増えてきた。今自分が持っている技術でも工夫次第ではうまくハンドリングできるようになるのではと、この操作を通して再確認できた。DBからjsonデータを生み出す関数は各ベンダーからもたくさんでているし、楽しみがふえるのかな。

一意になるデータの持ち方を考えるのが個人的には大切かと考えています。欠損値の扱いもうまくできたはず。nullをnullのまま残すことができた。自由なフォーマットなので、考慮できていないデータパターンがあるかもしれないですが、そのときはこそっりコメントください。

使い勝手的にはさらにラップして引数にファイル名を指定して使うようにするのかな。

夜勤明けなのに、頭痛いのに、仕事で使わないのに、引き下がれなくて頑張ってしまった。jsonデータ遊ぶのにきっと役に立つはず!!!

以上、ありがとうございました。😁

20191021追記

配列に複数のオブジェクト含むものに対応できていない。バグってる。ことはわかった。

[sqlite💞a06d1a5911c2 (月 10月 21 06:08:13) ~/script_scratch/jq]$cat c.json
{
  "Config": [
    {
      "Subnet": "172.17.0.0/16",
      "Gateway": "172.17.0.1"
    }
  ]
}
[sqlite💞a06d1a5911c2 (月 10月 21 06:07:40) ~/script_scratch/jq]$cat f.json
{
  "items": [
    {
      "item_id": 1,
      "name": "すてきな雑貨",
      "price": 2500
    },
    {
      "item_id": 2,
      "name": "格好いい置物",
      "price": 4500
    }
  ]
}
[sqlite💞a06d1a5911c2 (月 10月 21 06:07:48) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh <(cat c.json))
["Config.Gateway","172.17.0.1"]
["Config.Subnet","172.17.0.0/16"]
[sqlite💞a06d1a5911c2 (月 10月 21 06:07:54) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh <(cat f.json))
["items.item_id",2]
["items.name","格好いい置物"]
["items.price",4500]

これが原因だ。

[sqlite💞a06d1a5911c2 (月 10月 21 06:10:46) ~/script_scratch/jq]$./unify_type.sh f.json
{
  "items": {
    "item_id": 2,
    "name": "格好いい置物",
    "price": 4500
  }
}

こうするとうまくいくけどなー。配列がやだ。(\$in[$item]|normalize_json(.))(\$in[$item])に変更。

[sqlite💞a06d1a5911c2 (月 10月 21 06:22:03) ~/script_scratch/jq]$cat unify_type.sh 
#!/bin/bash 
#unify_type(){
#  jq '
#    def normalize_json(init_param__json):
#    init_param__json as $in
#    |if type=="array" then .[]|normalize_json(.)
#    elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item]|normalize_json(.))})
#    else . end;
#    normalize_json(.)
#  ' "$@"
#}

unify_type(){
  jq '
    def normalize_json(init_param__json):
    init_param__json as $in
    |if type=="array" then .[]|normalize_json(.)
    elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item])})
    else . end;
    normalize_json(.)
  ' "$@"
}

unify_type "$@"
[sqlite💞a06d1a5911c2 (月 10月 21 06:21:28) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh f.json)
["items",[{"item_id":1,"name":"すてきな雑貨","price":2500},{"item_id":2,"name":"格好いい置物","price":4500}]]

型統一はできないかやっぱ。

[sqlite💞a06d1a5911c2 (月 10月 21 06:32:37) ~/script_scratch/jq]$cat g.json
{
  "items": {
    {
      "item_id": 1,
      "name": "すてきな雑貨",
      "price": 2500
    },
    {
      "item_id": 2,
      "name": "格好いい置物",
      "price": 4500
    }
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 06:32:52) ~/script_scratch/jq]$cat f.json | jq '.'
{
  "items": [
    {
      "item_id": 1,
      "name": "すてきな雑貨",
      "price": 2500
    },
    {
      "item_id": 2,
      "name": "格好いい置物",
      "price": 4500
    }
  ]
}
[sqlite💞a06d1a5911c2 (月 10月 21 06:33:01) ~/script_scratch/jq]$cat g.json | jq '.'
parse error: Objects must consist of key:value pairs at line 7, column 6

なおった。

[sqlite💞a06d1a5911c2 (月 10月 21 06:46:45) ~/script_scratch/jq]$cat ./normalize_json.sh 
#!/bin/bash
#バグってるやつ
#normalize_json(){                                                                                                                                                                                         
#  jq  -c '
#  def normalize_json(init_param__json):
#    init_param__json as $kk
#    |to_entries
#    |if length==0 then [{key:null,value:null}] else . end
#    |.[]
#    |{key:($kk+.key),value:.value}
#    |.key as $prekey
#    |.
#    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
#  ;
#  normalize_json("")|[.key,.value]
#  ' "$@"
#}

#バグなおすステップ

#すてっぷ1
#normalize_json(){                                                                                                                                                                                         
#  jq '
#  def normalize_json(init_param__json):
#    init_param__json as $kk
#    |to_entries
#    |if length==0 then [{key:null,value:null}] else . end
#    |.[]
#    |{key:($kk+.key),value:.value}
#    |.key as $prekey
#    |.
#    |.value
#  ;
#  normalize_json("")
#  ' "$@"
#}

#すてっぷ2
#normalize_json(){                                                                                                                                                                                         
#  jq '
#  def normalize_json(init_param__json):
#    init_param__json as $kk
#    |to_entries
#    |if length==0 then [{key:null,value:null}] else . end
#    |.[]
#    |{key:($kk+.key),value:.value}
#    |.key as $prekey
#    |.
#    |.value
#    |.[]
#  ;
#  normalize_json("")
#  ' "$@"
#}

#すてっぷ3
#配列の場合は.valueではなく、.value|.[]で渡す
#normalize_json(){                                                                                                                                                                                         
#  jq '
#  def normalize_json(init_param__json):
#    init_param__json as $kk
#    |to_entries
#    |if length==0 then [{key:null,value:null}] else . end
#    |.[]
#    |{key:($kk+.key),value:.value}
#    |.key as $prekey
#    |.
#    |(select(.value|type!="array"))//(.value|.[]|normalize_json($prekey+"."))
#  ;
#  normalize_json("")
#  ' "$@"
#}

#すてっぷ4
#完成!
normalize_json(){                                                                                                                                                                                         
  jq -c '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |if length==0 then [{key:null,value:null}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="array"))//(.value|.[]|normalize_json($prekey+"."))
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json("")|[.key,.value]
  ' "$@"
}

normalize_json "$@"

デグレはなさそう。

[sqlite💞a06d1a5911c2 (月 10月 21 06:46:06) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh a.json)
["Containers.",null]
["Labels.",null]
["Options.",null]
[sqlite💞a06d1a5911c2 (月 10月 21 06:46:15) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh b.json)
["IPAM.Driver","default"]
["IPAM.Options",null]
["IPAM.Config.Subnet","172.17.0.0/16"]
["IPAM.Config.Gateway","172.17.0.1"]
[sqlite💞a06d1a5911c2 (月 10月 21 06:46:18) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh c.json)
["Config.Subnet","172.17.0.0/16"]
["Config.Gateway","172.17.0.1"]
[sqlite💞a06d1a5911c2 (月 10月 21 06:46:22) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh d.json)
["IPAM.Driver","default"]
["IPAM.Options",null]
[sqlite💞a06d1a5911c2 (月 10月 21 06:46:24) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh e.json)
["Gateway","172.17.0.1"]
["Subnet","172.17.0.0/16"]
[sqlite💞a06d1a5911c2 (月 10月 21 06:46:26) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh f.json)
["items.item_id",1]
["items.name","すてきな雑貨"]
["items.price",2500]
["items.item_id",2]
["items.name","格好いい置物"]
["items.price",4500]

key列とvalue列が2つあるわけだけど、行間参照してkey列の繰返しごとにグルーピングしておく必要があるな。
取得した結果をそのままの順序で、番号ふる必要がある。

[sqlite💞a06d1a5911c2 (月 10月 21 06:47:19) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh f.json)
["items.item_id",1]
["items.name","すてきな雑貨"]
["items.price",2500]
["items.item_id",2]
["items.name","格好いい置物"]
["items.price",4500]

強化テスト中

[sqlite💞a06d1a5911c2 (月 10月 21 06:56:40) ~/script_scratch/jq]$cat h.json
{
  "level": "info",
  "msg": "hello world",
  "time": "2019-09-01T07:03:28.99333+09:00"
}
[sqlite💞a06d1a5911c2 (月 10月 21 06:56:47) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh h.json)
["level","info"]
["msg","hello world"]
["time","2019-09-01T07:03:28.99333+09:00"]
[sqlite💞a06d1a5911c2 (月 10月 21 06:56:33) ~/script_scratch/jq]$cat i.json
[
  {
    "id": "1",
    "text": "text1 , test1"
  },
  {
    "id": "2",
    "text": "text2 , test2"
  }
]
[sqlite💞a06d1a5911c2 (月 10月 21 06:56:36) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh i.json)
["id","1"]
["text","text1 , test1"]
["id","2"]
["text","text2 , test2"]
[sqlite💞a06d1a5911c2 (月 10月 21 07:00:17) ~/script_scratch/jq]$cat j.json
{
  "message": null,
  "results": [
    {
      "address1": "香川県",
      "address2": "高松市",
      "address3": "",
      "kana1": "カガワケン",
      "kana2": "タカマツシ",
      "kana3": "",
      "prefcode": "37",
      "zipcode": "7600000"
    }
  ],
  "status": 200
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:00:28) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh j.json)
["message",null]
["results.address1","香川県"]
["results.address2","高松市"]
["results.address3",""]
["results.kana1","カガワケン"]
["results.kana2","タカマツシ"]
["results.kana3",""]
["results.prefcode","37"]
["results.zipcode","7600000"]
["status",200]
[sqlite💞a06d1a5911c2 (月 10月 21 07:01:23) ~/script_scratch/jq]$cat k.json
{
  "message": null,
  "results": [
    {
      "address1": "香川県",
      "address2": "高松市",
      "address3": "",
      "kana1": "カガワケン",
      "kana2": "タカマツシ",
      "kana3": "",
      "prefcode": "37",
      "zipcode": "7600000"
    },
    {
      "address1": "東京都",
      "address2": "足立区",
      "address3": "",
      "kana1": "トウキョウト",
      "kana2": "アダチク",
      "kana3": "",
      "prefcode": "13",
      "zipcode": "1200000"
    },
    {
      "address1": "千葉県",
      "address2": "千葉市中央区",
      "address3": "",
      "kana1": "チバケン",
      "kana2": "チバシチュウオウク",
      "kana3": "",
      "prefcode": "12",
      "zipcode": "2600000"
    }
  ],
  "status": 200
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:01:25) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh k.json)
["message",null]
["results.address1","香川県"]
["results.address2","高松市"]
["results.address3",""]
["results.kana1","カガワケン"]
["results.kana2","タカマツシ"]
["results.kana3",""]
["results.prefcode","37"]
["results.zipcode","7600000"]
["results.address1","東京都"]
["results.address2","足立区"]
["results.address3",""]
["results.kana1","トウキョウト"]
["results.kana2","アダチク"]
["results.kana3",""]
["results.prefcode","13"]
["results.zipcode","1200000"]
["results.address1","千葉県"]
["results.address2","千葉市中央区"]
["results.address3",""]
["results.kana1","チバケン"]
["results.kana2","チバシチュウオウク"]
["results.kana3",""]
["results.prefcode","12"]
["results.zipcode","2600000"]
["status",200]
[sqlite💞a06d1a5911c2 (月 10月 21 07:04:07) ~/script_scratch/jq]$cat l.json
{
  "a": "foo",
  "b": {
    "c": {
      "d": {
        "test": {
          "key": "value"
        }
      }
    }
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:04:09) ~/script_scratch/jq]$./normalize_json.sh <(./unify_type.sh l.json)
["a","foo"]
["b.c.d.test.key","value"]

パイプ対応。unifyいらなくってきたなー。ソート順の考慮が必要になってくるかもしれない。

[sqlite💞a06d1a5911c2 (月 10月 21 07:12:53) ~/script_scratch/jq]$cat normalize_json.sh 
#!/bin/bash
normalize_json(){                                                                                                                                                                                         
  jq -c '
  def normalize_json(init_param__json):
    init_param__json as $kk
    |to_entries
    |if length==0 then [{key:null,value:null}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="array"))//(.value|.[]|normalize_json($prekey+"."))
    |(select(.value|type!="object"))//(.value|normalize_json($prekey+"."))
  ;
  normalize_json("")|[.key,.value]
  ' "$@"
}

if [ -p /dev/stdin ]; then
  cat - | normalize_json "$@"
else
  normalize_json "$@"
fi
[sqlite💞a06d1a5911c2 (月 10月 21 07:14:36) ~/script_scratch/jq]$cat unify_type.sh 
#!/bin/bash 
unify_type(){
  jq '
    def normalize_json(init_param__json):
    init_param__json as $in
    |if type=="array" then .[]|normalize_json(.)
    elif type=="object" then reduce keys[] as $item ({};.+{($item):($in[$item])})
    else . end;
    normalize_json(.)
  ' "$@"
}
if [ -p /dev/stdin ]; then
  cat - | unify_type "$@"
else
  unify_type "$@"
fi

強化テスト中

[sqlite💞a06d1a5911c2 (月 10月 21 07:18:45) ~/script_scratch/jq]$url -s "https://qiita.com/api/v2/tags?page=1&per_page=100&sort=count" | jq -r '.[0:5] | .[]'
{
  "followers_count": 62726,
  "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/ceafd5ce024e312de9e893ce876ec89995ec3a7f/medium.jpg?1559694099",
  "id": "Python",
  "items_count": 32395
}
{
  "followers_count": 65160,
  "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/7e67c6a2d1e2fc39283fe28cfd5f065b93bbb15a/medium.jpg?1554725663",
  "id": "JavaScript",
  "items_count": 29022
}
{
  "followers_count": 36978,
  "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/6ce673b37ed24f77635fd6ec1b2664160c82b1c9/medium.jpg?1544599978",
  "id": "Ruby",
  "items_count": 23734
}
{
  "followers_count": 24244,
  "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/353fa0bbbfd79c4e8837b9c0b13232d80c32c6e8/medium.jpg?1554029821",
  "id": "Rails",
  "items_count": 19489
}
{
  "followers_count": 40569,
  "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/1420ca7652bf7c28bdab51be6f5bcd4640df27b1/medium.jpg?1478446216",
  "id": "PHP",
  "items_count": 17332
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:14:38) ~/script_scratch/jq]$curl -s "https://qiita.com/api/v2/tags?page=1&per_page=100&sort=count" | jq -r '.[0:5] | .[]' | ./unify_type.sh | ./normalize_json.sh 
["followers_count",62726]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/ceafd5ce024e312de9e893ce876ec89995ec3a7f/medium.jpg?1559694099"]
["id","Python"]
["items_count",32395]
["followers_count",65160]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/7e67c6a2d1e2fc39283fe28cfd5f065b93bbb15a/medium.jpg?1554725663"]
["id","JavaScript"]
["items_count",29022]
["followers_count",36978]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/6ce673b37ed24f77635fd6ec1b2664160c82b1c9/medium.jpg?1544599978"]
["id","Ruby"]
["items_count",23734]
["followers_count",24244]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/353fa0bbbfd79c4e8837b9c0b13232d80c32c6e8/medium.jpg?1554029821"]
["id","Rails"]
["items_count",19489]
["followers_count",40569]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/1420ca7652bf7c28bdab51be6f5bcd4640df27b1/medium.jpg?1478446216"]
["id","PHP"]
["items_count",17332]
[sqlite💞a06d1a5911c2 (月 10月 21 07:19:46) ~/script_scratch/jq]$cat m.json
{
  "pod": {
    "name": "mypod"
  },
  "containers": [
    {
      "name": "mycontainer1",
      "usage": {
            "cpu": "50"
          }
    },
    {
      "name": "mycontainer2",
      "usage": {
            "cpu": "100"
          }
    }
  ]
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:19:47) ~/script_scratch/jq]$cat m.json | ./unify_type.sh  | ./normalize_json.sh 
["containers.name","mycontainer1"]
["containers.usage.cpu","50"]
["containers.name","mycontainer2"]
["containers.usage.cpu","100"]
["pod.name","mypod"]
[sqlite💞a06d1a5911c2 (月 10月 21 07:22:06) ~/script_scratch/jq]$curl -s http://weather.livedoor.com/forecast/webservice/json/v1\?city\=016010 | jq .
{
  "pinpointLocations": [
    {
      "link": "http://weather.livedoor.com/area/forecast/0110000",
      "name": "札幌市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0121700",
      "name": "江別市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0122400",
      "name": "千歳市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0123100",
      "name": "恵庭市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0123400",
      "name": "北広島市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0123500",
      "name": "石狩市"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0130300",
      "name": "当別町"
    },
    {
      "link": "http://weather.livedoor.com/area/forecast/0130400",
      "name": "新篠津村"
    }
  ],
  "link": "http://weather.livedoor.com/area/forecast/016010",
  "forecasts": [
    {
      "dateLabel": "今日",
      "telop": "晴れ",
      "date": "2019-10-21",
      "temperature": {
        "min": null,
        "max": {
          "celsius": "19",
          "fahrenheit": "66.2"
        }
      },
      "image": {
        "width": 50,
        "url": "http://weather.livedoor.com/img/icon/1.gif",
        "title": "晴れ",
        "height": 31
      }
    },
    {
      "dateLabel": "明日",
      "telop": "晴時々曇",
      "date": "2019-10-22",
      "temperature": {
        "min": {
          "celsius": "9",
          "fahrenheit": "48.2"
        },
        "max": {
          "celsius": "19",
          "fahrenheit": "66.2"
        }
      },
      "image": {
        "width": 50,
        "url": "http://weather.livedoor.com/img/icon/2.gif",
        "title": "晴時々曇",
        "height": 31
      }
    }
  ],
  "location": {
    "city": "札幌",
    "area": "北海道",
    "prefecture": "道央"
  },
  "publicTime": "2019-10-21T05:00:00+0900",
  "copyright": {
    "provider": [
      {
        "link": "http://tenki.jp/",
        "name": "日本気象協会"
      }
    ],
    "link": "http://weather.livedoor.com/",
    "title": "(C) LINE Corporation",
    "image": {
      "width": 118,
      "link": "http://weather.livedoor.com/",
      "url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
      "title": "livedoor 天気情報",
      "height": 26
    }
  },
  "title": "道央 札幌 の天気",
  "description": {
    "text": " 北海道付近は、21日は高気圧に覆われますが、21日夜には気圧の谷が近づくでしょう。22日は気圧の谷の中となる見込みです。\n\n 石狩・空知・後志地方の21日3時の天気は、おおむね晴れています。\n\n 21日は、晴れでしょう。\n\n 22日は、晴れ朝晩曇りの見込みです。\n\n 海の波の高さは、21日は1メートルのち1.5メートルとやや高くなるでしょう。22日は1メートルの見込みです。",
    "publicTime": "2019-10-21T04:34:00+0900"
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:22:54) ~/script_scratch/jq]$curl -s http://weather.livedoor.com/forecast/webservice/json/v1\?city\=016010 | jq . | ./unify_type.sh | ./normalize_json.sh 
["copyright.provider.link","http://tenki.jp/"]
["copyright.provider.name","日本気象協会"]
["copyright.link","http://weather.livedoor.com/"]
["copyright.title","(C) LINE Corporation"]
["copyright.image.width",118]
["copyright.image.link","http://weather.livedoor.com/"]
["copyright.image.url","http://weather.livedoor.com/img/cmn/livedoor.gif"]
["copyright.image.title","livedoor 天気情報"]
["copyright.image.height",26]
["description.text"," 北海道付近は、21日は高気圧に覆われますが、21日夜には気圧の谷が近づくでしょう。22日は気圧の谷の中となる見込みです。\n\n 石狩・空知・後志地方の21日3時の天気は、おおむね晴れています。\n\n 21日は、晴れでしょう。\n\n 22日は、晴れ朝晩曇りの見込みです。\n\n 海の波の高さは、21日は1メートルのち1.5メートルとやや高くなるでしょう。22日は1メートルの見込みです。"]
["description.publicTime","2019-10-21T04:34:00+0900"]
["forecasts.dateLabel","今日"]
["forecasts.telop","晴れ"]
["forecasts.date","2019-10-21"]
["forecasts.temperature.min",null]
["forecasts.temperature.max.celsius","19"]
["forecasts.temperature.max.fahrenheit","66.2"]
["forecasts.image.width",50]
["forecasts.image.url","http://weather.livedoor.com/img/icon/1.gif"]
["forecasts.image.title","晴れ"]
["forecasts.image.height",31]
["forecasts.dateLabel","明日"]
["forecasts.telop","晴時々曇"]
["forecasts.date","2019-10-22"]
["forecasts.temperature.min.celsius","9"]
["forecasts.temperature.min.fahrenheit","48.2"]
["forecasts.temperature.max.celsius","19"]
["forecasts.temperature.max.fahrenheit","66.2"]
["forecasts.image.width",50]
["forecasts.image.url","http://weather.livedoor.com/img/icon/2.gif"]
["forecasts.image.title","晴時々曇"]
["forecasts.image.height",31]
["link","http://weather.livedoor.com/area/forecast/016010"]
["location.city","札幌"]
["location.area","北海道"]
["location.prefecture","道央"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0110000"]
["pinpointLocations.name","札幌市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0121700"]
["pinpointLocations.name","江別市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0122400"]
["pinpointLocations.name","千歳市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0123100"]
["pinpointLocations.name","恵庭市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0123400"]
["pinpointLocations.name","北広島市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0123500"]
["pinpointLocations.name","石狩市"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0130300"]
["pinpointLocations.name","当別町"]
["pinpointLocations.link","http://weather.livedoor.com/area/forecast/0130400"]
["pinpointLocations.name","新篠津村"]
["publicTime","2019-10-21T05:00:00+0900"]
["title","道央 札幌 の天気"]
[sqlite💞a06d1a5911c2 (月 10月 21 07:28:31) ~/script_scratch/jq]$curl -s 'https://qiita.com/api/v2/users/tag1216/following_tags?per_page=2' | jq .
[
  {
    "followers_count": 22,
    "icon_url": null,
    "id": "Pyspark",
    "items_count": 61
  },
  {
    "followers_count": 496,
    "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/2b32f68777d6f0d2d88609a3efb979378fc97320/medium.jpg?1401964762",
    "id": "Spark",
    "items_count": 634
  }
]
[sqlite💞a06d1a5911c2 (月 10月 21 07:28:36) ~/script_scratch/jq]$curl -s 'https://qiita.com/api/v2/users/tag1216/following_tags?per_page=2' | jq . | ./unify_type.sh | ./normalize_json.sh 
["followers_count",22]
["icon_url",null]
["id","Pyspark"]
["items_count",61]
["followers_count",496]
["icon_url","https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/2b32f68777d6f0d2d88609a3efb979378fc97320/medium.jpg?1401964762"]
["id","Spark"]
["items_count",634]
[sqlite💞a06d1a5911c2 (月 10月 21 07:28:38) ~/script_scratch/jq]$

バグ発見!

[sqlite💞a06d1a5911c2 (月 10月 21 07:30:22) ~/script_scratch/jq]$curl -s https://httpbin.org/json | jq .
{
  "slideshow": {
    "author": "Yours Truly",
    "date": "date of publication",
    "slides": [
      {
        "title": "Wake up to WonderWidgets!",
        "type": "all"
      },
      {
        "items": [
          "Why <em>WonderWidgets</em> are great",
          "Who <em>buys</em> WonderWidgets"
        ],
        "title": "Overview",
        "type": "all"
      }
    ],
    "title": "Sample Slide Show"
  }
}
[sqlite💞a06d1a5911c2 (月 10月 21 07:30:31) ~/script_scratch/jq]$curl -s https://httpbin.org/json | jq . | ./unify_type.sh | ./normalize_json.sh 
["slideshow.author","Yours Truly"]
["slideshow.date","date of publication"]
["slideshow.slides.title","Wake up to WonderWidgets!"]
["slideshow.slides.type","all"]
jq: error (at <stdin>:21): string ("Why <em>Wo...) has no keys
[sqlite💞a06d1a5911c2 (月 10月 21 07:35:53) ~/script_scratch/jq]$cat n.json | jq .
{
  "items": [
    "Why <em>WonderWidgets</em> are great",
    "Who <em>buys</em> WonderWidgets"
  ],
  "title": "Overview",
  "type": "all"
}
jq: error (at <stdin>:8): string ("Why <em>Wo...) has no keys

How do I use jq to convert number to string?

keyをもつかどうか確認する。

[sqlite💞a06d1a5911c2 (月 10月 21 08:03:53) ~/script_scratch/jq]$vi normalize_json.sh 
[sqlite💞a06d1a5911c2 (月 10月 21 08:04:43) ~/script_scratch/jq]$cat n.json
{
  "items": [
    "Why <em>WonderWidgets</em> are great",
    "Who <em>buys</em> WonderWidgets"
  ],
  "title": "Overview",
  "type": "all"
}
[sqlite💞a06d1a5911c2 (月 10月 21 08:04:54) ~/script_scratch/jq]$cat n.json | jq . | ./unify_type.sh | ./normalize_json.sh 
jq: error (at <stdin>:8): string ("Why <em>Wo...) has no keys

このフォーマットで考えてみる。

[sqlite💞a06d1a5911c2 (月 10月 21 20:39:32) ~/script_scratch/jq]$cat o.json | jq .
[
  "Why <em>WonderWidgets</em> are great",
  "Who <em>buys</em> WonderWidgets"
]

keysプロパティはインデックス配列を返却してくれる。
keys[]プロパティはタプルを返却してくれる。

[sqlite💞a06d1a5911c2 (月 10月 21 20:40:09) ~/script_scratch/jq]$cat o.json | jq '.|keys'
[
  0,
  1
]
[sqlite💞a06d1a5911c2 (月 10月 21 20:43:56) ~/script_scratch/jq]$cat o.json | jq 'keys[]'
0
1

これをもとにkey:value形式に変形したい。\$item[$index]のように事前に取得して置いた配列をインデックス参照で取得するようにしている。

[sqlite💞a06d1a5911c2 (月 10月 21 20:44:01) ~/script_scratch/jq]$cat o.json | jq '. as $item|foreach keys[] as $index({};{key:$index,value:$item[$index]})'
{
  "key": 0,
  "value": "Why <em>WonderWidgets</em> are great"
}
{
  "key": 1,
  "value": "Who <em>buys</em> WonderWidgets"
}
[sqlite💞a06d1a5911c2 (月 10月 21 21:59:14) ~/script_scratch/jq]$./normalize_json.sh n.json
["items",["Why <em>WonderWidgets</em> are great","Who <em>buys</em> WonderWidgets"]]
["title","Overview"]
["type","all"]

てか、これあるじゃん....

jq だけじゃない JSON ツールのご紹介

201910122追記

階段カンマ区切り文字列リストも出来た。

reduceにするとonelineにforeachはリニアに蓄積。

[sqlite💔a06d1a5911c2 (火 10月 22 14:03:47) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|xargs -n10|tr ' ' ',') | jq -c 'map(tostring)|foreach .[] as $item("";.+","+$item)'
",1"
",1,2"
",1,2,3"
",1,2,3,4"
",1,2,3,4,5"
",1,2,3,4,5,6"
",1,2,3,4,5,6,7"
",1,2,3,4,5,6,7,8"
",1,2,3,4,5,6,7,8,9"
",1,2,3,4,5,6,7,8,9,10"
[sqlite💔a06d1a5911c2 (火 10月 22 14:04:08) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|tac|xargs -n10|tr ' ' ',') | jq -c 'map(tostring)|foreach .[] as $item("";.+","+$item)'
",10"
",10,9"
",10,9,8"
",10,9,8,7"
",10,9,8,7,6"
",10,9,8,7,6,5"
",10,9,8,7,6,5,4"
",10,9,8,7,6,5,4,3"
",10,9,8,7,6,5,4,3,2"
",10,9,8,7,6,5,4,3,2,1"
[sqlite💔a06d1a5911c2 (火 10月 22 14:05:33) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|xargs -n10|tr ' ' ',') | jq -c 'map(tostring)|reduce .[] as $item("";.+","+$item)'
",1,2,3,4,5,6,7,8,9,10"
[sqlite💔a06d1a5911c2 (火 10月 22 14:04:27) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|tac|xargs -n10|tr ' ' ',') | jq -c 'map(tostring)|reduce .[] as $item("";.+","+$item)'
",10,9,8,7,6,5,4,3,2,1"

配列も蓄積できた。最終行だけほしかったら、reduceで。

[sqlite💔a06d1a5911c2 (火 10月 22 14:08:55) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10) | xargs -n10 | sed 's;^;[;' | sed 's;$;];' | tr ' ' ',' | jq -c 'foreach .[] as $item([];.+$item)'
[1]
[1,2]
[1,2,3]
[1,2,3,4]
[1,2,3,4,5]
[1,2,3,4,5,6]
[1,2,3,4,5,6,7]
[1,2,3,4,5,6,7,8]
[1,2,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8,9,10]
[sqlite💔a06d1a5911c2 (火 10月 22 14:09:00) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|tac) | xargs -n10 | sed 's;^;[;' | sed 's;$;];' | tr ' ' ',' | jq -c 'foreach .[] as $item([];.+$item)'
[10]
[10,9]
[10,9,8]
[10,9,8,7]
[10,9,8,7,6]
[10,9,8,7,6,5]
[10,9,8,7,6,5,4]
[10,9,8,7,6,5,4,3]
[10,9,8,7,6,5,4,3,2]
[10,9,8,7,6,5,4,3,2,1]
[sqlite💔a06d1a5911c2 (火 10月 22 14:10:37) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10) | xargs -n10 | sed 's;^;[;' | sed 's;$;];' | tr ' ' ',' | jq -c 'reduce .[] as $item([];.+$item)'
[1,2,3,4,5,6,7,8,9,10]
[sqlite💔a06d1a5911c2 (火 10月 22 14:09:20) ~/script_scratch/jq]$printf "[%s]\n" $(seq 10|tac) | xargs -n10 | sed 's;^;[;' | sed 's;$;];' | tr ' ' ',' | jq -c 'reduce .[] as $item([];.+$item)'
[10,9,8,7,6,5,4,3,2,1]

key:value形式でもいけるはず。

[sqlite💔a06d1a5911c2 (火 10月 22 14:17:41) ~/script_scratch/jq]$printf "{"@key@":%s,"@value@":%s}\n" $(seq 20) | xargs -n10 | sed 's;^;[;' | sed 's;$;];'  | tr ' ' ',' | sed 's;@;";g' | jq .
[
  {
    "key": 1,
    "value": 2
  },
  {
    "key": 3,
    "value": 4
  },
  {
    "key": 5,
    "value": 6
  },
  {
    "key": 7,
    "value": 8
  },
  {
    "key": 9,
    "value": 10
  },
  {
    "key": 11,
    "value": 12
  },
  {
    "key": 13,
    "value": 14
  },
  {
    "key": 15,
    "value": 16
  },
  {
    "key": 17,
    "value": 18
  },
  {
    "key": 19,
    "value": 20
  }
]
[sqlite💔a06d1a5911c2 (火 10月 22 14:34:28) ~/script_scratch/jq]$printf "{"@key@":%s,"@value@":%s}\n" $(seq 20) | xargs -n10 | sed 's;^;[;' | sed 's;$;];'  | tr ' ' ',' | sed 's;@;";g' | jq -cr 'map(tostring)|foreach .[]  as $item("";.+","+$item)' | sed 's;\\;;g' 
,{"key":1,"value":2}
,{"key":1,"value":2},{"key":3,"value":4}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10},{"key":11,"value":12}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10},{"key":11,"value":12},{"key":13,"value":14}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10},{"key":11,"value":12},{"key":13,"value":14},{"key":15,"value":16}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10},{"key":11,"value":12},{"key":13,"value":14},{"key":15,"value":16},{"key":17,"value":18}
,{"key":1,"value":2},{"key":3,"value":4},{"key":5,"value":6},{"key":7,"value":8},{"key":9,"value":10},{"key":11,"value":12},{"key":13,"value":14},{"key":15,"value":16},{"key":17,"value":18},{"key":19,"value":20}

key名とvalue名もダイナミックに出来た

[sqlite💔a06d1a5911c2 (火 10月 22 14:56:11) ~/script_scratch/jq]$parallel -N2 echo -ne "@\{\\\"key{1}\\\":{1},\\\"value{2}\\\":{2}\}" ::: $(seq 20|xargs -n10)  | sed 's;^@;[;' | sed 's;$;];' | sed 's;@;,;g'  |jq-cr 'map(tostring)|foreach .[] as $item("";.+","+$item)'

parallelコマンド便利

[sqlite💔a06d1a5911c2 (火 10月 22 14:56:11) ~/script_scratch/jq]$parallel -N2 echo -ne "@\{\\\"key{1}\\\":{1},\\\"value{2}\\\":{2}\}" ::: $(seq 20|xargs -n10)  | sed 's;^@;[;' | sed 's;$;];' | sed 's;@;,;g'  |jq-cr 'map(tostring)|foreach .[] as $item("";.+","+$item)'
,{"key1":1,"value2":2}
,{"key1":1,"value2":2},{"key3":3,"value4":4}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12},{"key13":13,"value14":14}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12},{"key13":13,"value14":14},{"key15":15,"value16":16}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12},{"key13":13,"value14":14},{"key15":15,"value16":16},{"key17":17,"value18":18}
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12},{"key13":13,"value14":14},{"key15":15,"value16":16},{"key17":17,"value18":18},{"key19":19,"value20":20}
[sqlite💔a06d1a5911c2 (火 10月 22 14:58:37) ~/script_scratch/jq]$parallel -N2 echo -ne "@\{\\\"key{1}\\\":{1},\\\"value{2}\\\":{2}\}" ::: $(seq 20|tac|xargs -n10)  | sed 's;^@;[;' | sed 's;$;];' | sed 's;@;,;g' |jq -cr 'map(tostring)|foreach .[] as $item("";.+","+$item)'
,{"key20":20,"value19":19}
,{"key20":20,"value19":19},{"key18":18,"value17":17}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9},{"key8":8,"value7":7}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9},{"key8":8,"value7":7},{"key6":6,"value5":5}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9},{"key8":8,"value7":7},{"key6":6,"value5":5},{"key4":4,"value3":3}
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9},{"key8":8,"value7":7},{"key6":6,"value5":5},{"key4":4,"value3":3},{"key2":2,"value1":1}

蓄積結果の最終行のみほしかったら、reduceで。

[sqlite💔a06d1a5911c2 (火 10月 22 14:59:29) ~/script_scratch/jq]$parallel -N2 echo -ne "@\{\\\"key{1}\\\":{1},\\\"value{2}\\\":{2}\}" ::: $(seq 20|tac|xargs -n10)  | sed 's;^@;[;' | sed 's;$;];' | sed 's;@;,;g' |jq -cr 'map(tostring)|reduce .[] as $item("";.+","+$item)'
,{"key20":20,"value19":19},{"key18":18,"value17":17},{"key16":16,"value15":15},{"key14":14,"value13":13},{"key12":12,"value11":11},{"key10":10,"value9":9},{"key8":8,"value7":7},{"key6":6,"value5":5},{"key4":4,"value3":3},{"key2":2,"value1":1}
[sqlite💔a06d1a5911c2 (火 10月 22 14:59:37) ~/script_scratch/jq]$parallel -N2 echo -ne "@\{\\\"key{1}\\\":{1},\\\"value{2}\\\":{2}\}" ::: $(seq 20|xargs -n10)  | sed 's;^@;[;' | sed 's;$;];' | sed 's;@;,;g'  |jq-cr 'map(tostring)|reduce .[] as $item("";.+","+$item)'
,{"key1":1,"value2":2},{"key3":3,"value4":4},{"key5":5,"value6":6},{"key7":7,"value8":8},{"key9":9,"value10":10},{"key11":11,"value12":12},{"key13":13,"value14":14},{"key15":15,"value16":16},{"key17":17,"value18":18},{"key19":19,"value20":20}

key:value形式に正規化するスクリプト

jqコマンドのpaths関数はkeyに紐づくvalueまでのパスを一意に表示してくるので、とっても便利。
それを使ってダイナミックに文字列組立しています。keyに紐づく値が存在するものと存在しないもので、出力を区別しようとしたけど、むずかしかった。例えばvalueに[],{}のみが含まれている場合は、出力条件にtype=="array"ないしはtype=="object"とすれば出力できるようになるけど、それのメリットより紐づくものほとんど全てを表示してしまうので、出力がうるさくなる。デバッグにはいい。

[sqlite💔a06d1a5911c2 (火 10月 22 17:51:38) ~/script_scratch/jq]$cat normalize_json_改改.sh 
#!/bin/bash

exec 2>&1

normalize_json(){
  FILE="$@"
  START_MSG="printf \"ファイル[%s]の処理を開始します。\n\" @ "
  END_MSG="printf \"ファイル[%s]の処理を終了します。\n\" @ "
  RESULT_FMT="printf \"%s=%s\n\" KEY VALUE"
  NO_RESULT_FMT="printf \"%s=%s\n\" KEY VALUE"
  CMD="cat $FILE | jq -cr '@'"

  echo "$START_MSG" | sed "s;@;$FILE;" | bash
  cat "$FILE" | jq -r 'paths|map(if type=="number" then "["+tostring+"]" else "."+tojson end)|join("")' | sed 's;^\.;;' | sed 's;^;\.;' |\
    while read ln;do
      #デバッグ用
      #cat $FILE | jq '.'
      #tojson関数は記号文字をエスケープしてくれるので、便利
      #デバッグ用
      #RESULT=$(cat $FILE | jq -cr "$ln|if type==\"string\" or type==\"number\" or type==\"null\" or type==\"boolean\" or type==\"array\" or type==\"object\" then tojson else empty end" | sed '/^$/d')
      RESULT=$(cat $FILE | jq -cr "$ln|if type==\"string\" or type==\"number\" or type==\"null\" or type==\"boolean\" then tojson else empty end" | sed '/^$/d')
      #ドッと関数は直前の標準入力をそのまま出力してくれるので、事故る
      #RESULT=$(cat $FILE | jq -cr "$ln|if type==\"string\" or type==\"number\" then . else empty end" | sed '/^$/d')
      #デバッグ用
      #printf "%s \n" "$(echo "$CMD" | sed "s;@;$ln;")"
      if [ "MATCH$RESULT" != "MATCH" ]; then
        echo "$RESULT_FMT"  | sed "s;KEY;$ln;" | sed "s;VALUE;$RESULT;" | bash
      else
        echo "$NO_RESULT_FMT"  | sed "s;KEY;$ln;" | sed "s;VALUE;$RESULT;" | bash
      fi
    done
  echo "$END_MSG" | sed "s;@;$FILE;" | bash
}

if [ -p /dev/stdin ]; then
  cat - | normalize_json "$@"
else
  normalize_json "$@"
fi

このように出力されていた結果が

[sqlite💔a06d1a5911c2 (火 10月 22 17:46:07) ~/script_scratch/jq]$ls {a..b}.json | xargs -n1 ./normalize_json_改改.sh
ファイル[a.json]の処理を開始します。
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Labels=
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Containers=
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Options=
ファイル[a.json]の処理を終了します。
ファイル[b.json]の処理を開始します。
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Driver=default
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Options=null
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0]=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0].Subnet=172.17.0.0/16
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0].Gateway=172.17.0.1
ファイル[b.json]の処理を終了します。

このようになる。デバッグにはいいかも。

[sqlite💔a06d1a5911c2 (火 10月 22 17:47:58) ~/script_scratch/jq]$ls {a..b}.json | xargs -n1 ./normalize_json_改改.sh
ファイル[a.json]の処理を開始します。
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Labels={}
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Containers={}
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}
.Options={}
ファイル[a.json]の処理を終了します。
ファイル[b.json]の処理を開始します。
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM=Driver:default
Options:null=Config:[Subnet:172.17.0.0/16]
Config:[Gateway:172.17.0.1]=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Driver=default
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Options=null
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config=[Subnet:172.17.0.0/16]
[Gateway:172.17.0.1]=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0]=Subnet:172.17.0.0/16
Gateway:172.17.0.1=
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0].Subnet=172.17.0.0/16
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
.IPAM.Config[0].Gateway=172.17.0.1
ファイル[b.json]の処理を終了します。

スカラ値のみに出力を絞り込んだ方が、いいのかな。

ちなみにgronコマンドの出力結果はこんな感じになる。gronコマンド使おうかな。

[sqlite💔a06d1a5911c2 (火 10月 22 17:59:50) ~/script_scratch/jq]$ls {a..b}.json | xargs -n1 -I@ bash -c 'echo file.name = @&& cat @ | gron' | sed -E 's/\{\}|\[\]/null/;s/;$//'
file.name = a.json
json = null
json.Containers = null
json.Labels = null
json.Options = null
file.name = b.json
json = null
json.IPAM = null
json.IPAM.Config = null
json.IPAM.Config[0] = null
json.IPAM.Config[0].Gateway = "172.17.0.1"
json.IPAM.Config[0].Subnet = "172.17.0.0/16"
json.IPAM.Driver = "default"
json.IPAM.Options = null
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした