JSONの全キーを再帰関数でツリー表示する（Python用）

　JSONはとても便利なデータ形式ですが、構造の自由度が大きいのでその全体像を掴みづらい時があります。特に、辞書とリストが何層にも入り混じって複雑な木構造になっているやつは、目的のデータにたどり着くために何回もキーを指定する必要があります。全体のツリー構造とキーと代表値がわかれば便利なので、そのような関数を書きました。既にそのようなモジュールがあったらすいません。

　とりあえずコードだけ先に

```python
def json_tree(data,indent=0):
    space = ' '*indent
    if type(data) == dict:
        for k in data.keys():
            print('\n',space,k,end='')
            json_tree(data[k],indent+4)
    elif type(data) == list and len(data) > 0:
        json_tree(data[0],indent+4)
    else:
        print(' :',data,end='')
    return
```

# JSONとは

　Python流の解釈をすれば、「辞書またはリストまたはその他の値を含む「辞書またはリストまたはその他の値（以下無限ループ）」」です。つまり、**「辞書またはリストまたはその他の値」**をJSONと定義すれば、JSONとは再帰的に**「JSONを含むJSON」**と定義することができます。

　まあ、そんな言葉遊びみたいなことは置いておいて、

- 辞書型でキー・バリューのペアを設定してもよい
- 辞書型をリストにしてデータベースみたいにしてもいい

　というルールで、階層的なデータをキーで指定しやすくしたものと考えることができます。

　つまり、図示すると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/6752402b-ba8e-4dec-d2c9-404419c6022b.png)

　つまり、経路はどうあれ、最終的には必ず末端の「その他の値」に行き着きます。リスト型は通常、データベースのように同じキーを持つ辞書型が列挙されていることが想定されているので、その一行目を共通の辞書型として扱うことができます。

　よって、

- 辞書型を経由した時点で、インデントを下げて、全キーに対し、キーを表示しそのバリューを解析
- リスト型を経由した時点で、その一行目を解析
- その他の値を経由した時点で、値を表示

　という手順を繰り返せば、いい感じにキー・バリューペアのツリー構造を表示できそうです。

# 再帰関数として実装

　下の図は、再帰関数を組むにあたって、上の流れをもう少し具体的に図示したものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/65719f82-1aab-a083-8ee5-b95a1e817c5c.png)

　一種の例外処理として、リストの長さが 0 だった時には、それをあたかも「空のリストという値」であるかのふるまうようにします。こうしないと、空のリストがあった時に、一行空けて無が出力されてしまうので。また、改行が行われる時は「新たなキーが検出された時」だけなので、それに気をつければ以下のような実装になります。

```python
def json_tree(data,indent=0):
    space = ' '*indent
    if type(data) == dict:
        for k in data.keys():
            print('\n',space,k,end='')
            json_tree(data[k],indent+4)
    elif type(data) == list and len(data) > 0:
        json_tree(data[0],indent+4)
    else:
        print(' :',data,end='')
    return
```

　[ランダムに生成されたJSON](https://www.json-generator.com/)に対して試してみると、以下のような結果が得られます。

```python

  _id : 60ef08e86b3a1582f0d06063
  index : 0
  guid : 076489f7-98a3-489e-807b-5e43fbd816a8
  isActive : True
  balance : $2,974.36
  picture : http://placehold.it/32x32
  age : 22
  eyeColor : blue
  name : Concetta Stewart
  gender : female
  company : BEZAL
  email : concettastewart@bezal.com
  phone : +1 (959) 448-2023
  address : 656 Wortman Avenue, Interlochen, South Dakota, 9988
  about : Officia ipsum veniam ex aute. # 長いので省略

  registered : 2017-09-24T11:13:26 -09:00
  latitude : 2.283491
  longitude : 35.920546
  tags : dolore
  friends
      id : 0
      name : Johnston Rosales
  greeting : Hello, Concetta Stewart! You have 8 unread messages.
  favoriteFruit : strawberry
```

　辞書型の中にリストを含むような場合（上の`friends`）でもちゃんと木構造を検出できていることがわかります。

　ついでに、あるゲームで使われているjsonデータを対象にした例。

```python

  _version : 2.2.0
  _customData
      _time : 74
      _BPMChanges : []
      _bookmarks
          _time : 8
          _name : 1A
  _events : []
  _notes
      _time : 8.0625
      _lineIndex : 1
      _lineLayer : 0
      _type : 0
      _cutDirection : 1
  _obstacles
      _time : 8
      _lineIndex : 3
      _type : 0
      _duration : 4
      _width : 1
  _waypoints : []
```

　空のリストがあった時に、ちゃんとバリューとして`[]`が返されていることがわかります。