今さらPython3 (53) - JSON

この本の第8章継続中。HTMLは次の章でやるらしいので飛ばして、本日のお題はJSON

入門 Python 3

入門 Python 3

JSON

JSONは、Java Scriptのサブセットだけども、Pythonでも用いられることも多いと。確かに、以前Pythonを触っていたときも、ちょいちょい顔を出していたような気がする。

>>> menu = \
... {
... "breakfast": {
...         "hours":"7-11",
...         "items": {
...                  "breakfast burritos": "$6.00",
...                  "pancakes": "$4.00"
...                  }
...         },
... "lunch": {
...         "hours":"11-3",
...         "items": {
...                  "humburger": "$5.00"
...                  }
...         },
... "dinner": {
...         "hours": "3-10",
...         "items": {
...                  "spaghetti": "$8.00"
...                  }
...           }
... }
>>> menu
{'breakfast': {'items': {'pancakes': '$4.00', 'breakfast burritos': '$6.00'}, 'hours': '7-11'}, 'lunch': {'items': {'humburger': '$5.00'}, 'hours': '11-3'}, 'dinner': {'items': {'spaghetti': '$8.00'}, 'hours': '3-10'}}
>>> 

それっぽくインデント付けて作ったけど、結局のところは入れ子の辞書を作ったんだね。

>>> import json
>>> menu_json = json.dumps(menu)
>>> menu_json
'{"breakfast": {"items": {"pancakes": "$4.00", "breakfast burritos": "$6.00"}, "hours": "7-11"}, "lunch": {"items": {"humburger": "$5.00"}, "hours": "11-3"}, "dinner": {"items": {"spaghetti": "$8.00"}, "hours": "3-10"}}'
>>> 

これでjson文字列に変換したとあるけど、ぱっと見変わったところと言えば、外側に''が付いて文字列風になったこと、あとはクオテーションの関係だろうけど、中のシングルクオテーションがダブル("")になった事ぐらいかな。

>>> type(menu_json)
<class 'str'>

てか、文字列風ではなく、ホントに文字列だわw。

>>> menu2 = json.loads(menu_json)
>>> menu2 == menu
True
>>> menu2
{'breakfast': {'items': {'pancakes': '$4.00', 'breakfast burritos': '$6.00'}, 'hours': '7-11'}, 'lunch': {'items': {'humburger': '$5.00'}, 'hours': '11-3'}, 'dinner': {'items': {'spaghetti': '$8.00'}, 'hours': '3-10'}}

冷静に考えると当たり前で、拡張子jsonとか付いていたとしても、中身はテキストファイルな訳で、それを読み込んで、あたかも意味のあるデータの塊(Pythonでは入れ子の辞書)に変換するのが目的だもんね。

>>> import datetime
>>> now = datetime.datetime.utcnow()
>>> now
datetime.datetime(2015, 12, 23, 1, 57, 7, 936685)
>>> json.dumps(now)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: datetime.datetime(2015, 12, 23, 1, 57, 7, 936685) is not JSON serializable
>>> 

この記事が相当以前に書かれたことがバレてしまうことはさておき、datetime型の値をそのままJSONに変換できないと怒られているわけですね。これを意外に思って欲しいんだろうけど、あ、辞書から変換するのねと思っていた矢先なので、唐突感は否めない。

>>> now_str = str(now)
>>> json.dumps(now_str)
'"2015-12-23 01:57:07.936685"'
>>> from time import mktime
>>> now_epoch = int(mktime(now.timetuple()))
>>> json.dumps(now_epoch)
'1450803427'

datetimeからは直接JSONに変換できないので、str()を使って文字列としてハンドルするか、ローカル時刻からUnix時刻(1970年1月1日0:00:00AM(UTC)から何秒後かを表す)に変換して、JSONが理解できる形として取り込んでいる訳ですね。

>>> class DTEncoder(json.JSONEncoder):
...     def default(self, obj):
...         #check type of obj
...         if isinstance(obj, datetime.datetime):
...             return int(mktime(obj.timetuple()))
...         #otherwise process normally
...         return json.JSONEncoder.default(self, obj)
... 
>>> json.dumps(now, cls=DTEncoder)
'1450803427'
>>> type(now)
<class 'datetime.datetime'>
>>> 

毎回手作業が面倒ならば、こんな感じでjson.JSONEncoderを継承したクラスを作って、defaultメソッドをオーバーライドして、独自のロジックを通らせることも出来ますよって事だね。json.dumps()の第2引数にクラスが指定できるようになっている事からも明らかですと。nowはdatetime.datetimeだからisinstanceのところでTrueが返るのでUNIX時刻に変換してから処理して、それ以外の場合は継承元のdefaultメソッドを実行して結果を返す。なるほど。

>>> isinstance(now, datetime.datetime)
True
>>> type(234)
<class 'int'>
>>> isinstance(234, int)
True
>>> type('hey')
<class 'str'>
>>> isinstance('hey', str)
True
>>> 

つづきにisinstance()の説明も載ってたのね。。。

(つづく)