[Python] ネストされた辞書からkeyのリストでvalueを取り出す

# 1. はじめに

書籍情報APIから返ってくるjsonデータが思った以上に扱いにくくて、個別にキーを指定して必要な情報を抽出するのは面倒だったので、キーを羅列したリストを用意して、for文で回せるようにしてみました。

# 2. ネストされた辞書の例

## 2.1. 書籍情報APIから返ってくるjsonデータ

例として以下のコードで返ってくるjsonデータを見てみますが、蔵書目録を作るという程度の目的であれば、必要なデータは一部で済むことになります。

```Python:ISBNを使ってAPIから書籍情報を抽出
import json
import urllib
import pprint

url = f'https://api.openbd.jp/v1/get?isbn=9784873117584'
urlopen = urllib.request.urlopen(url)
bookdata = json.loads(urlopen.read().decode('utf-8'))
pprint.pprint(bookdata)
```

```json:openBDから返ってきたjsonデータ
[{'hanmoto': {'datecreated': '2016-08-31 16:02:14',
              'datemodified': '2016-08-31 16:02:14'},
  'onix': {'CollateralDetail': {'TextContent': [{'ContentAudience': '00',
                                                 'Text': 'ディープラーニングの本格的な入門書。外部のライブラリに頼らずPython '
                                                         '3によってゼロからディープラーニングを作成できる。',
                                                 'TextType': '02'},
                                                {'ContentAudience': '00',
                                                 'Text': '実際にシステムを作りながらディープラーニングを学ぶ！\n'
                                                         'ディープラーニングの本格的な入門書。外部のライブラリに頼らずに、Python '
                                                         '3によってゼロからディープラーニングを作ることで、ディープラーニングの原理を楽しく学びます。ディープラーニングやニューラルネットワークの基礎だけでなく、誤差逆伝播法や畳み込みニューラルネットワークなども実装レベルで理解できます。ハイパーパラメータの決め方や重みの初期値といった実践的なテクニック、Batch '
                                                         'NormalizationやDropout、Adamといった最近のトレンド、自動運転や画像生成、強化学習などの応用例、さらには、なぜディープラーニングは優れているのか？ '
                                                         'なぜ層を深くすると認識精度がよくなるのか？ '
                                                         'といった“Why”に関する問題も取り上げます。',
                                                 'TextType': '03'}]},
           'DescriptiveDetail': {'Audience': [{'AudienceCodeType': '22',
                                               'AudienceCodeValue': '00'}],
                                 'Contributor': [{'ContributorRole': ['A01'],
                                                  'PersonName': {'collationkey': 'サイトウ '
                                                                                 'コウキ',
                                                                 'content': '斎藤 '
                                                                            '康毅'},
                                                  'SequenceNumber': '1'}],
                                 'Extent': [{'ExtentType': '11',
                                             'ExtentUnit': '03',
                                             'ExtentValue': '320'}],
                                 'Language': [{'CountryCode': 'JP',
                                               'LanguageCode': 'jpn',
                                               'LanguageRole': '01'}],
                                 'ProductComposition': '00',
                                 'ProductForm': 'BA',
                                 'ProductFormDetail': 'B108',
                                 'Subject': [{'MainSubject': '',
                                              'SubjectCode': '3055',
                                              'SubjectSchemeIdentifier': '78'},
                                             {'SubjectCode': '20',
                                              'SubjectSchemeIdentifier': '79'}],
                                 'TitleDetail': {'TitleElement': {'Subtitle': {'collationkey': 'パイソンデマナブディープラーニングノリロントジッソウ',
                                                                               'content': 'Pythonで学ぶディープラーニングの理論と実装'},
                                                                  'TitleElementLevel': '01',
                                                                  'TitleText': {'collationkey': 'ゼロカラツクルディープラーニング',
                                                                                'content': 'ゼロから作るDeep '
                                                                                           'Learning'}},
                                                 'TitleType': '01'}},
           'NotificationType': '03',
           'ProductIdentifier': {'IDValue': '9784873117584',
                                 'ProductIDType': '15'},
           'ProductSupply': {'SupplyDetail': {'Price': [{'CurrencyCode': 'JPY',
                                                         'PriceAmount': '3400',
                                                         'PriceType': '03'}],
                                              'ProductAvailability': '99',
                                              'ReturnsConditions': {'ReturnsCode': '03',
                                                                    'ReturnsCodeType': '04'}}},
           'PublishingDetail': {'Imprint': {'ImprintIdentifier': [{'IDValue': '87311',
                                                                   'ImprintIDType': '19'},
                                                                  {'IDValue': '0742',
                                                                   'ImprintIDType': '24'}],
                                            'ImprintName': 'オライリー・ジャパン'},
                                'Publisher': {'PublisherIdentifier': [{'IDValue': '274',
                                                                       'PublisherIDType': '19'},
                                                                      {'IDValue': '0742',
                                                                       'PublisherIDType': '24'}],
                                              'PublisherName': '株式会社オーム社',
                                              'PublishingRole': '01'},
                                'PublishingDate': [{'Date': '20160924',
                                                    'PublishingDateRole': '01'}]},
           'RecordReference': '9784873117584'},
  'summary': {'author': '斎藤康毅／著',
              'cover': '',
              'isbn': '9784873117584',
              'pubdate': '20160924',
              'publisher': 'オライリー・ジャパン',
              'series': '',
              'title': 'ゼロから作るDeep Learning',
              'volume': ''}}]
```

## 2.2. 書籍情報APIのjsonデータを扱う上での問題点

書籍情報APIのjsonデータの特徴として以下の点が挙げられます。

1. ネストが深い
1. dictとlistが混ざっている
1. キーが常に存在するとは限らない

そして上記の理由から、以下のような問題が発生します。

1. キーを事前に用意してfor文で回すことができない
1. キーが存在しない場合に`try-except`で補足しないと`KeyError`になる

そのため上記問題点を解消するために、辞書のキーと配列のインデックスをまとめたリストを用意しておいて、valueを取り出す関数を検討しました。

# 3. 作成した関数


```Python:
def get_value_from_json(self, parent_data, key_list):
    value = ''
    for key in key_list:
        if type(parent_data) is dict:
            value = parent_data.get(key, '')
        elif type(parent_data) is list and type(key) is int:
            value = parent_data[key] if key < len(parent_data) else ''
        else:
            value = ''
        if value == '':
            break
        parent_data = value
    return value
```

```Python:出力例
get_value_from_json(bookdata, [])
# 

get_value_from_json(bookdata, [0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'TitleText', 'content'])
# 'ゼロから作るDeep Learning'

get_value_from_json(bookdata, [0, 'summary', 'author'])
# '斎藤康毅／著'

get_value_from_json(bookdata, ['null']) # ※存在しないキー
# 
```

* 関数では最初に`result = None`として、第二引数のリストが空だった場合に`''`を返します(Noneでもいいですが、処理によっては文字列の`None`が情報として入るので`''`の方が使い勝手が良い気がしています)。
* for文でキーorインデックスのリストを1つずつ処理していきますが、`value`の型を`dict`か`list`か判定して、キーかインデックスが有効な値であれば`value`を`result`に代入していき、最後に代入した`value`を返します。
* キーorインデックスが有効でなければ`result`には`None`を代入して、for文を`break`して、`None`を返します。

# 4. 活用例

この関数を用意しておくことで、以下のような形で書籍情報APIのjsonデータから情報を抽出できます。

```Python:
keys = [[0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'TitleText', 'content'],
        [0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'Subtitle', 'content'],
        [0, 'summary', 'author'],
        [0, 'summary', 'publisher'],
        [0, 'onix', 'CollateralDetail', 'TextContent', 0, 'Text'],
        [0, 'summary', 'pubdate'],
        [0, 'summary', 'isbn']]

for key in keys:
    print(get_value_from_json(bookdata, key))
```
```:出力
ゼロから作るDeep Learning
Pythonで学ぶディープラーニングの理論と実装
斎藤康毅／著
オライリー・ジャパン
ディープラーニングの本格的な入門書。外部のライブラリに頼らずPython 3によってゼロからディープラーニングを作成できる。
20160924
9784873117584
```