Pythonで,自分で定義したクラスのオブジェクトを Json 形式にする (encode) 方法とJson形式から戻す (decode) 方法のメモ.
参考にしたページ:
* Pythonのjsonモジュールドキュメント
* How to use custom Python JSON serializers and deserializers to automatically roundtrip complex types
例
こんなクラスを例にする.
import json
from datetime import date
class MyA:
def __init__(self, i=0, d=date.today()):
self.i = i # int型
self.d = d # date型
Jsonデータ
まず始めに,オブジェクトをどのような形式でJsonデータにするのかを考えなくてはならない.MyA オブジェクトは,'i'
と'd'
をキーとした object にするのが自然に思える.また,date型のデータは,文字列にしても良いが,手っ取り早く toordinal メソッドで整数にしてしまおう.するとたとえば
a = MyA(i=100, d=date(year=2345, month=6, day=12))
と定義した a は,{"i": 100, "d": 856291}
というJsonデータに変換されることになる.これでも良いこともあるだろうが,しかし,これでは,デコードするときに,どの型に戻せば良いかわからなくて面倒になる.そこで,'_type'
というキーにクラス名を格納し,値は'value'
というキーに格納する.その結果として,Jsonデータは次のようになる:
{ "_type": "MyA",
"value": {"i": 100,
"d": { "_type": "date",
"value": 856291}}}
エンコード
Python の json ライブラリの dump や dumps コマンドは,cls という引数をとる.ここに,自分で定義したエンコーダを指定することができる.JSONEncoder というのが普通使われるクラスなので,これをベースとして自分のエンコーダを作る.実際に必要なことは,default というメソッドの再定義である.
class MyEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, date):
return {'_type': 'date', 'value': o.toordinal()}
if isinstance(o, MyA):
return {'_type': 'MyA', 'value': o.__dict__}
return json.JSONEncoder.default(self, o)
ご覧のように,各クラスが直接見ているところだけ書けばよくて,再帰するのはライブラリがよしなにやってくれる.(MyAクラスのオブジェクトのときには,返すものは辞書であって,そこには date型のデータが設定されていても良い.)
次のように実行できる.
a = MyA(100, date(year=2345, month=6, day=12))
js = json.dumps(a, cls=MyEncoder)
print(js)
実行結果:
{"_type": "MyA", "value": {"i": 100, "d": {"_type": "date", "value": 856291}}}
デコード
こちらも,JSONDecoderというクラスをベースにデコーダを作る.今度は,親クラスであるJSONDecoderのオブジェクトが作成される際に,object_hook という変数に渡す関数の中で,必要なデコードが行われるように書く.以下のような具合である:
class MyDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook,
*args, **kwargs)
def object_hook(self, o):
if '_type' not in o:
return o
type = o['_type']
if type == 'date':
return date.fromordinal(o['value'])
elif type == 'MyA':
return MyA(**o['value'])
object_hook の o としては,普通にデコードされた結果が渡ってくる.上の例だと '_type'
だの 'value'
だのをキーに持つ辞書が来るので,それを適切なクラスのオブジェクトに作り替えるようにコードを書けば良い.ここでもまた,再帰処理のことは考えなくても大丈夫.
エンコードの時の実行に引き続いてやってみる:
b = json.loads(js, cls=MyDecoder)
print(b.i, b.d)
実行結果:
100 2345-06-12