3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Python でクラスオブジェクトをJSON形式にする・戻す

Last updated at Posted at 2020-11-13

Pythonで,自分で定義したクラスのオブジェクトを Json 形式にする (encode) 方法とJson形式から戻す (decode) 方法のメモ.
参考にしたページ:

こんなクラスを例にする.

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データに変換することになる.

この方式は,状況によっては,少し良くない.Json データを見た時に,これが MyA クラスのオブジェクトであることが直ちにはわからないので,デコードが簡単にいかない.そこで,定義を修正して,'_type' というキーにクラス名を格納し,値は'value'というキーに格納することにする.この方式だと,Jsonデータは次のようになる:

{ "_type": "MyA", 
  "value": {"i": 100, 
            "d": { "_type": "date",
                   "value": 856291}}}

MyA オブジェクトだけではなく,date オブジェクトについても _type を使っているところに注意.以下,この方式を例に,エンコードとデコードを行ってみる.

エンコード

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 オブジェクトを処理する (*) のところを見ると,_typevalue をキーにする辞書を作っている.value の値として,例えば上の a ならば,{"i": 100, "d": date(year=2345, month=6, day=12)} という辞書を設定している.つまり,再帰処理はライブラリがよしなに行ってくれるのである.

次のように実行できる.

    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 としては,エンコードの時に作成したもの (MyEncoder::default の返り値) が渡ってくる.上の例だと,MyA オブジェクトや date オブジェクトについては '_type' だの 'value' だのをキーに持つ辞書が来る.それを適切なクラスのオブジェクトに作り替えるようにコードを書けば良い.

エンコードの時の実行に引き続いてやってみる:

    b = json.loads(js, cls=MyDecoder)
    print(b.i, b.d)

実行結果:

100 2345-06-12
3
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?