LoginSignup
1
0

More than 3 years have passed since last update.

JSON内に文字列として埋め込まれたJSONをjqで展開する

Posted at

こちらの記事を見て「自分も最近似たようなことやったなぁ」と思ったので、他の方法も含めてメモがてら書いておきます。

なお、扱う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には本当にいろいろな機能が搭載されているので、マニュアルに目を通してみると新しい発見があるかもしれません。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0