【プログラミング】多階層の辞書を一階層の辞書に変換する(Python3)
大変久しぶりにブログを書きます。最近は研究プロジェクトが再開したり、個人的な勉強会で数学を学ぶ機会を持ったり、仕事も変わったりと色々とありました。
今年の目標としては「書くこと」であるので、今回も研究プロジェクトで必要になったコードをこちらでご紹介します。
今回、DBにそのままバルクインサートすることも躊躇してしまうほど、某APIから全くぐちゃぐちゃなJSONデータが得られました。 jsonパッケージをそのまま使うと、Pythonのディクショナリ形式に変換することができるのですが、 しかしながら、中身がぐちゃぐちゃ・・・keyの値も正規化されておりません。
今回は、anaconda3-2.4.0
によってインストールされる、Python3.5やその他パッケージを利用して実行します。
まず、JSONデータの中身は下記の通り・・・ (JSON Data Set Sampleより引用)
{ "id": "0001", "type": "donut", "name": "Cake", "image": { "url": "images/0001.jpg", "width": 200, "height": 200 }, "thumbnail": { "url": "images/thumbnails/0001.jpg", "width": 32, "height": 32 } }
これを全て一階層で取れるようにしたいと思います。
そのためには、「再帰」を使って、ディクショナリ内から、keyがなくなるまで取得し続けるということを繰り返します。
while
文でも書くことができるのですが、コードを短く済ませることができ、便利なので再帰を使いましょう。
まずはそこまでのパスを取得する関数です。
# 複雑なディクショナリから、どんなKeyがあるのか再帰的に取り出す。 # 引数は、対象の辞書、ルート(最下階層)、区切り文字です。 # 利用例:get_dict_hierarchy(cake_image,'','_') def get_dict_hierarchy(target_dict,root_path,sep): if isinstance(target_dict,dict): for key in target_dict.keys(): value = target_dict[key] target_path = root_path + sep + key # valueが辞書じゃない場合にはパスを返す。 if not isinstance(value,dict): yield target_path[1:] # 辞書の場合にはもう一度探索する。 else: yield from get_dict_hierarchy(value,target_path,sep)
ループのところ、target_dict.items()でやってもいいかもしれません。そうすると一行削れるかもしれないですね。 ちなみに上のデータで実行すると下記のようになります。
In[2]: cake_dict = { "id": "0001", "type": "donut", "name": "Cake", "image": { "url": "images/0001.jpg", "width": 200, "height": 200 }, "thumbnail": { "url": "images/thumbnails/0001.jpg", "width": 32, "height": 32 } } In[3]: list(get_dict_hierarchy(cake_dict,'','_')) Out[3]: ['thumbnail_width', 'thumbnail_height', 'thumbnail_url', 'image_width', 'image_height', 'image_url', 'type', 'id', 'name']
これで階層ごとのデータを取れました。(データはジェネレータなので、リストに直して表示しています。) では、それぞれの階層からディクショナリを深堀し、ターゲットの値を取得して、 一階層からなるディクショナリに変換します。
掘っていく関数は下記の通り。
# 階層が深いディクショナリを掘っていき、ターゲットの値を取得する。 # 対象となるディクショナリ、探したいデータまでのパス、区切り文字 # 利用例:dig_dict(cake_dict,'thumbnail_url','_') def dig_dict(target_dict,target_branch,sep): limbs = target_branch.split(sep) leaf = target_dict for one_limb in limbs: leaf = leaf[one_limb] return leaf
では、データを作りましょう。
In[5]: flat_dict = {} In[6]: for one_branch in list(get_dict_hierarchy(cake_dict,'','_')): flat_dict[one_branch] = dig_dict(cake_dict,one_branch,'_') In[7]: flat_dict Out[7]: {'id': '0001', 'image_height': 200, 'image_url': 'images/0001.jpg', 'image_width': 200, 'name': 'Cake', 'thumbnail_height': 32, 'thumbnail_url': 'images/thumbnails/0001.jpg', 'thumbnail_width': 32, 'type': 'donut'}
と、お目当てのデータが手に入りました。
再帰を使ってうまくネストを解いたり、JSONデータの解析をおこなったりすることもあるでしょう。
APIからくるデータは汚いことが多いですが、まずこれらの関数を使ってみて、 どういう結果が得られるか確かめてみるのもいかがでしょうか。