Pythonでjsonをフラットマップに変換する

## 1. はじめに

jsonの`object`や`array`等の入れ子の任意データに一回でアクセスしたい要件があったため、キーでアクセス可能なマップ（Dict）に変換する処理を作成しました。

**(仕様)：必要に応じて変更してください。**

* 入れ子はキーの階層構造で示す。
* 階層の区切りは`/`とする。
* `array`の要素数は`COUNT`で示す。

## 2. ソースコード

```python:flatMap.py
# -*- coding: utf-8 -*-
import json
import datetime

# ★ポイント1
SEPARATOR = '/'
COUNT = 'COUNT'

# ★ポイント2
def convert_flat_map(keyStr, target, map):

    # ★ポイント3
    if isinstance(target, dict):
        for k in target.keys():
            convert_flat_map(keyStr + SEPARATOR + k, target[k], map)

    # ★ポイント4
    elif isinstance(target, list):
        map[keyStr + SEPARATOR + COUNT] = str(len(target))
        for i in range(len(target)):
            one = target[i]
            convert_flat_map(keyStr + SEPARATOR + str(i), one, map)

    # ★ポイント5
    # 参考（デバッグ）として分岐を分けているだけ。
    # 全て map[keyStr] = target で構わない。
    elif isinstance(target, int):
        map[keyStr] = target

    elif isinstance(target, float):
        map[keyStr] = target

    elif isinstance(target, datetime.date):
        map[keyStr] = target

    elif isinstance(target, unicode):
        map[keyStr] = target

    elif isinstance(target, str):
        map[keyStr] = target

    elif isinstance(target, bool):
        map[keyStr] = target

    elif target is None:
        map[keyStr] = target

    else:
        print('<other>' + str(target))

# test case 1 : http://json-schema.org/example/address.json
def test01():
    # ★ポイント6
    f = open('address.json', 'r')
    jsonData = json.load(f)
    map = {}
    convert_flat_map('', jsonData, map)
    for k in sorted(map.keys()):
        print(k + ' = ' + str(map[k]))

# test case 2 : http://json-schema.org/example/calendar.json
def test02():
    f2 = open('calendar.json', 'r')
    jsonData2 = json.load(f2)
    map2 = {}
    convert_flat_map('', jsonData2, map2)
    sorted(map2)
    for k in sorted(map2.keys()):
        print(k + ' = ' + map2[k])

if __name__=='__main__':

    # test
    test01()
    print("------------------------------")
    test02()
```

**★ポイント1**
入れ子の区切り文字と`array`の要素数を示す文字列を定義します。各自の要件に応じて変更してください。

**★ポイント2**
今回のポイントです。入れ子や`array`はその内部をさらに取り出すため、再帰処理で呼び出せるように関数を定義しました。

* 第１引数　：　マップ（Dict）のキーとなる文字列
* 第２引数　：　解析対象のjsonデータ（Dict）
* 第３引数　：　結果を格納するマップ（Dict）

**★ポイント3**
jsonデータの`object`はpythonでは`Dict`になるので、格納されている要素に対して`convert_flat_map()`関数を再帰的に呼び出します。

**★ポイント4**
jsonデータの`array`はpythonでは`list`になるので、格納されている要素に対して`convert_flat_map()`関数を再帰的に呼び出します。
また、`list`の場合は★ポイント1で定義した文字列で要素数も結果のマップ（Dict）に格納します。

**★ポイント5**
jsonデータで`object`と`array`以外は入れ子の終端となるので、結果のマップ（Dict）に格納します。
jsonのデータについては https://www.json.org/json-ja.html を参照ください。

**（注意）**
**jsonのデータ型で条件分岐を分けていますがデバッグのためです。必要に応じて★ポイント5の条件分岐は纏めてください。**

**★ポイント6**
動作確認用の処理です。呼び出す際は`convert_flat_map()`の第１引数に空文字を指定します。
なお、ここで第１引数に文字列を指定した場合、キーのプレフィックスとして付与されます。

## 3. 動作確認

* テスト①　：　http://json-schema.org/example/address.json

```json:address.json
{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "description": "An Address following the convention of http://microformats.org/wiki/hcard",
    "type": "object",
    "properties": {
        "post-office-box": { "type": "string" },
        "extended-address": { "type": "string" },
        "street-address": { "type": "string" },
        "locality":{ "type": "string" },
        "region": { "type": "string" },
        "postal-code": { "type": "string" },
        "country-name": { "type": "string"}
    },
    "required": ["locality", "region", "country-name"],
    "dependencies": {
        "post-office-box": ["street-address"],
        "extended-address": ["street-address"]
    }
}
```

```console:address.jsonの変換結果
/$schema = http://json-schema.org/draft-06/schema#
/dependencies/extended-address/0 = street-address
/dependencies/extended-address/COUNT = 1
/dependencies/post-office-box/0 = street-address
/dependencies/post-office-box/COUNT = 1
/description = An Address following the convention of http://microformats.org/wiki/hcard
/properties/country-name/type = string
/properties/extended-address/type = string
/properties/locality/type = string
/properties/post-office-box/type = string
/properties/postal-code/type = string
/properties/region/type = string
/properties/street-address/type = string
/required/0 = locality
/required/1 = region
/required/2 = country-name
/required/COUNT = 3
/type = object
```

* テスト②　：　http://json-schema.org/example/calendar.json

```json:calendar.json
{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "description": "A representation of an event",
    "type": "object",
    "required": [ "dtstart", "summary" ],
    "properties": {
        "dtstart": {
            "format": "date-time",
            "type": "string",
            "description": "Event starting time"
        },
        "dtend": {
            "format": "date-time",
            "type": "string",
            "description": "Event ending time"
        },
        "summary": { "type": "string" },
        "location": { "type": "string" },
        "url": { "type": "string", "format": "uri" },
        "duration": {
            "format": "time",
            "type": "string",
            "description": "Event duration"
        },
        "rdate": {
            "format": "date-time",
            "type": "string",
            "description": "Recurrence date"
        },
        "rrule": {
            "type": "string",
            "description": "Recurrence rule"
        },
        "category": { "type": "string" },
        "description": { "type": "string" },
        "geo": { "$ref": "http://json-schema.org/geo" }
    }
}
```

```console:calendar.jsonの変換結果
/$schema = http://json-schema.org/draft-06/schema#
/description = A representation of an event
/properties/category/type = string
/properties/description/type = string
/properties/dtend/description = Event ending time
/properties/dtend/format = date-time
/properties/dtend/type = string
/properties/dtstart/description = Event starting time
/properties/dtstart/format = date-time
/properties/dtstart/type = string
/properties/duration/description = Event duration
/properties/duration/format = time
/properties/duration/type = string
/properties/geo/$ref = http://json-schema.org/geo
/properties/location/type = string
/properties/rdate/description = Recurrence date
/properties/rdate/format = date-time
/properties/rdate/type = string
/properties/rrule/description = Recurrence rule
/properties/rrule/type = string
/properties/summary/type = string
/properties/url/format = uri
/properties/url/type = string
/required/0 = dtstart
/required/1 = summary
/required/COUNT = 2
/type = object
```

## 4. さいごに

今回はjsonをフラットマップ（Dict）に変換する処理について説明しました。この処理が必要かどうかは要件によって異なりますが、１つのキーを利用して１回で任意のデータにアクセスできるのは個人的には便利かと思います。