こちらの記事を見て「自分も最近似たようなことやったなぁ」と思ったので、他の方法も含めてメモがてら書いておきます。
なお、扱うJSONデータについては、上記記事と同じものを使うことにします。
--raw-output
/-r
オプションを使う方法
まずは記事と同じやりかた。
-r
で結果の文字列をJSONエンコードされていない生の文字列として出力して、それをさらにjq
でフォーマットする方法。
cat message.json | jq -r '.Cause' | jq .
{
"errorMessage": "An error occurred (InvalidElasticIpFault) when calling the RestoreFromClusterSnapshot operation: The Elastic IP address aa.bbb.ccc.ddd is already associated with another instance.",
"errorType": "InvalidElasticIpFault",
"stackTrace": [
" 省略\n",
" File \"/var/runtime/botocore/client.py\", line 316, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/runtime/botocore/client.py\", line 635, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
ちなみにjq -r
の時点ではこういう結果になります。
cat message.json | jq -r '.Cause'
{"errorMessage": "An error occurred (InvalidElasticIpFault) when calling the RestoreFromClusterSnapshot operation: The Elastic IP address aa.bbb.ccc.ddd is already associated with another instance.", "errorType": "InvalidElasticIpFault", "stackTrace": [" 省略\n", " File \"/var/runtime/botocore/client.py\", line 316, in _api_call\n return self._make_api_call(operation_name, kwargs)\n", " File \"/var/runtime/botocore/client.py\", line 635, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"]}
つまり、Cause
の中身をそのまま文字列として取り出せば、それがちょうどJSONなので、続けてjq .
にかけられるということですね。
fromjson
を使う方法
tojson
, fromjson
というフィルタを使うと、JSONエンコードしたりJSONデコードすることができます。
fromjson
を目的の.Cause
にかけることで文字列からJSONに展開した結果が得られます。
cat message.json | jq '.Cause | fromjson'
{
"errorMessage": "An error occurred (InvalidElasticIpFault) when calling the RestoreFromClusterSnapshot operation: The Elastic IP address aa.bbb.ccc.ddd is already associated with another instance.",
"errorType": "InvalidElasticIpFault",
"stackTrace": [
" 省略\n",
" File \"/var/runtime/botocore/client.py\", line 316, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/runtime/botocore/client.py\", line 635, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
おまけ: 全体を維持したままCause
だけ展開する
|=
(update-assignment)を使うと、任意の部分を差し替えることができます。
これとfromjson
を組み合わせることで、Cause
の部分をfromjson
で展開したものに差し替えたJSON全体が得られます。
cat message.json | jq '.Cause |= fromjson'
{
"Error": "InvalidElasticIpFault",
"Cause": {
"errorMessage": "An error occurred (InvalidElasticIpFault) when calling the RestoreFromClusterSnapshot operation: The Elastic IP address aa.bbb.ccc.ddd is already associated with another instance.",
"errorType": "InvalidElasticIpFault",
"stackTrace": [
" 省略\n",
" File \"/var/runtime/botocore/client.py\", line 316, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/runtime/botocore/client.py\", line 635, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
}
おわりに
JSONの中に文字列としてJSONを入れるなんておかしなことだ、最初からJSONの一部として入れておけばいいのに、と思う人もいるかもしれませんが、さまざまな理由によってこうなるケースが時々あるのです。
- 任意のデータが入る箇所にたまたまJSONが入るケース
- 内容は定義されていなくて、任意のデータが入れられる
- 何らかの処理結果や別のシステムなどから得られたデータをそのまま詰めるケース
- 様々な種類のJSONが入るが、型を定義しづらい、インターフェース的には感知したくないケース
- その場合、中身を扱うレイヤーではJSON Schema等を使って妥当性を検証する
- 別のフォーマットだったものをJSONに変換したケース
などなど
こういった厄介なJSONと向き合うときは、jq
のマニュアルを一読してみることをおすすめします。
jqには本当にいろいろな機能が搭載されているので、マニュアルに目を通してみると新しい発見があるかもしれません。