この記事は約14分17秒で読むことができます。

jqコマンドとbashコマンドでjson形式をドット記法へ変換した話

はじめに

json形式をドット記法に変換したいなーと思ってググっていました。qiitaにアップされていた記事をもとに少し加工してドット形式に変換してみようと思います。ことの発端はoracle19cr3にjsonデータ投入したいなーと思ったからです。19cr3からかなりjson力入れているようなので、データ入れて弄りたいなと思ってそのやり方を探っていました。色んな電文形式としてjsonが扱われているようですが、そういったデータをsqlで処理したいがための前処理みたいな位置付けです。なので、深入りはしていません。json形式はなかみはなんとなく知っていたよぐらいの人ですので、もっといいやり方あれば、コメント欲しいです。bashも再入門し始めて、徐々に思い出し始めている最中です。jqコマンドははじめて7日。。難しかったけど、粘りました。。。たぶん役に立つはず!!!

参考文献

jqで階層構造を持つオブジェクトをCSVにマップする  
reduceを極めてデータ・フォーマットを自由自在に変換  
コマンドラインJSONプロセッサー jqの演算子と関数  
JSONにコメントを書いて前処理で消す  
jq: jsonのkey/valueの追加、arrayへ要素追加  

jsonデータ

ポイントはタイプを確認しながら追うことだと思う。

空のjsonをもつjson

コード表示

[oracle@centos ~]$ cat ng2p.json
{
  "Labels": {},
  "Containers": {},
  "Options": {}
}

jsonオブジェクト配列をもつjson

コード表示

[oracle@centos ~]$ cat ng1p.json
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}

関数は2つ。fmt関数とdig関数。

どちらもユーザー定義関数。単体テストできるようにbashスクリプトに1つのfunctionを切り出しました。呼び出した後、整形処理をしています。

コード表示

[oracle@centos ~]$ cat dig.sh
#!/bin/bash 
dig(){
  jq '
  def dig(k):
    . as $in
    |if type=="array" then .[]|dig(.)
    elif type=="object" then
       reduce keys[] as $key 
         ({};
             .
             +{
                ($key):($in[$key]|dig(.))
              }
         )
     else . end;
  dig(.)
  ' "$@"
}
[oracle@centos ~]$ cat fmt.sh
#!/bin/bash
fmt(){                                                                                                                                                                                         
  jq -c '
  def fmt(k):
    k as $kk
    |to_entries
    |if length==0 then [{key:null,value:"dummy"}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|fmt($prekey+"."))
  ;
  fmt("")|[.key]
  ' "$@"
}
[oracle@centos ~]$ cat caller.sh
#!/bin/bash
source ./dig.sh
source ./fmt.sh

fmt <<<$(dig "$@") | sed -E 's/\]|\[|"//g' | sed -e 's/\.$//'

実行例

fmt関数では空jsonをもつケースに備え、dummyでjsonオブジェクトをaddしています。dig関数では配列が来た場合は先にラップを剥し、全てのオブジェクトをjsonオブジェクトに変換しています。そのあとで、fmtでkey:value形式のjsonオブジェクトに変換しています。jqのビルトイン関数でwalk関数がありますが、それと似たような考え方で、与えられたjsonデータを透過的に歩き回り、各要素の内部状態をkey:value形式のjsonオブジェクトに変換していくことで、定義した関数のインターフェース(引数)が単一になり、扱いやすくなるのではと思いました。

コード表示

[oracle@centos ~]$ ./caller.sh ng1p.json
IPAM.Config.Gateway
IPAM.Config.Subnet
IPAM.Driver
IPAM.Options
[oracle@centos ~]$ ./caller.sh ng2p.json
Containers
Labels
Options

透過的に歩き回って内部状態を変える??

dig関数を少しこれの説明用に変えます。dig “$@”を追加しただけ。

コード表示

[oracle@centos ~]$ cat dig.sh
#!/bin/bash 
dig(){
  jq '
  def dig(k):
    . as $in
    |if type=="array" then .[]|dig(.)
    elif type=="object" then
       reduce keys[] as $key 
         ({};
             .
             +{
                ($key):($in[$key]|dig(.))
              }
         )
     else . end;
  dig(.)
  ' "$@"
}
dig "$@"

配列がなくなりました。

コード表示

[oracle@centos ~]$ cat ng1p.json
{
  "IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
      {
        "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
      }
    ]
  }
}
[oracle@centos ~]$ ./dig.sh ng1p.json
{
  "IPAM": {
    "Config": {
      "Gateway": "172.17.0.1",
      "Subnet": "172.17.0.0/16"
    },
    "Driver": "default",
    "Options": null
  }
}

caller.shとfmt.shを少し変えます。fmt関数の呼び出し後、[.key,.value]を追加しました。

コード表示

[oracle@centos ~]$ cat caller.sh
#!/bin/bash
source ./dig.sh
source ./fmt.sh

fmt <<<$(dig "$@")
[oracle@centos ~]$ cat fmt.sh
#!/bin/bash
fmt(){                                                                                                                                                                                         
  jq -c '
  def fmt(k):
    k as $kk
    |to_entries
    |if length==0 then [{key:null,value:"dummy"}] else . end
    |.[]
    |{key:($kk+.key),value:.value}
    |.key as $prekey
    |.
    |(select(.value|type!="object"))//(.value|fmt($prekey+"."))
  ;
  fmt("")|[.key,.value]
  ' "$@"
}
[oracle@centos ~]$ ./caller.sh ng1p.json
{
  "IPAM": {
    "Config": {
      "Gateway": "172.17.0.1",
      "Subnet": "172.17.0.0/16"
    },
    "Driver": "default",
    "Options": null
  }
}
["IPAM.Config.Gateway","172.17.0.1"]
["IPAM.Config.Subnet","172.17.0.0/16"]
["IPAM.Driver","default"]
["IPAM.Options",null]
[oracle@centos ~]$ ./caller.sh ng2p.json
{
  "Containers": {},
  "Labels": {},
  "Options": {}
}
["Containers.","dummy"]
["Labels.","dummy"]
["Options.","dummy"]

あとがき

もっと複雑なjsonデータにうまく動くかは怪しいですが、様子見です。これで、jsonデータをoracleに投入できそう。以上、ありがとうございました。

Leave a Reply

Your email address will not be published. Required fields are marked *