Python
JSON

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

1. はじめに

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

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

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

2. ソースコード

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はその内部をさらに取り出すため、再帰処理で呼び出せるように関数を定義しました。

  • 第1引数 : マップ(Dict)のキーとなる文字列
  • 第2引数 : 解析対象のjsonデータ(Dict)
  • 第3引数 : 結果を格納するマップ(Dict)

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

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

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

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

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

3. 動作確認

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"]
    }
}
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
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" }
    }
}
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)に変換する処理について説明しました。この処理が必要かどうかは要件によって異なりますが、1つのキーを利用して1回で任意のデータにアクセスできるのは個人的には便利かと思います。