Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

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
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away