概要
- json.dumpなどのメソッドに、クラスのインスタンスを直接投げ込んでjson化したい。
- 何もせずにこれをやると、TypeErrorになる。
- エンコーダのdefaultメソッドを書き換えて、インスタンスの
__dict__
を返すようにすると、とりあえずうまくいく。- メソッドがあっても無視してプロパティだけ出力してくれる。
- 循環参照のチェックもそのまま動く。
実践
大体上に答えを書きましたが、こんなコードを書いてみました、テストデータを組み立てている部分の方が長いです。
import json
# テスト用のクラス、メソッド付き
class MyClass:
def my_method(self):
return True
# テスト用のクラス、ネストして使う用
class ChildClass:
pass
parent = MyClass()
child = ChildClass()
child.value1 = 'value'
child.value2 = 11
parent.child = child
parent.childList=[child, child, child]
# 以下のような構造になっている
# MyClass{
# child = ChildClass{
# value1:'value1',
# value2:11
# }
# childList=[child, child, child]
# }
# ここがポイント
def default_method(item):
if isinstance(item, object) and hasattr(item, '__dict__'):
return item.__dict__
else:
raise TypeError
# 作成したメソッドを指定してdumps
print(json.dumps(parent, default=default_method, indent=2))
isinstance(item, object)
以外にもhasattr(item, '__dict__')
しているのは、全てのobjectが__dict__
を持っているわけではないためのガードです。1
他にも、default_methodの部分ではDecimal型の処理やそのほか、素のままでは処理してくれない型をエンコードしたいケースでも使用するので、クラス中に色々な型が出てくる場合、ちょっと中身が大変になりそうです。
なお、json.dumpsの内部で使われるJsonEncoderは、エンコードするオブジェクトが循環参照を起こしている場合にエラーを返しますが、そのチェックもちゃんと機能します。
出力
{
"child": {
"value1": "value",
"value2": 11
},
"childList": [
{
"value1": "value",
"value2": 11
},
{
"value1": "value",
"value2": 11
},
{
"value1": "value",
"value2": 11
}
]
}
こんなふうに、ネストしたインスタンスやインスタンスのリストも無事に出力されました。ごく単純な構造でしか試していないし、pythonはまだ修業中の身なので、これでは扱い切れないケースがあるかもしれませんが、少ない手数でjson化できるのは悪くないと思います。
-
__dict__
に触るのは厳密には好ましくないようで、例えばクラスが__getattribute__
などをオーバーライドしている場合、__dict__
に直接アクセスすると__getattribute__
は呼び出されません。これを厳密に処理したい場合、__dir__
で諸々の名前を取得してうまくやる…しか思いつきませんが、大変なのでここではやりません。 ↩