Object of type ・・・ is not JSON serializable
日付やsetなどを含む辞書をjsonモジュールのdumpでJSON文字列に変換すると、「Object of type ・・・ is not JSON serializable」というエラーが発生します。
import datetime import json data = dict() data["id"] = 1 data["datetime"] = datetime.datetime.now() json.dumps(data) # Object of type datetime is not JSON serializable
JSONは型として日時を表す型がサポートされていないため、明示的にどういう文字列に変換するか、というロジックを指定する必要があります。なおJSONでサポートされている型は以下のとおりです。
- オブジェクト(辞書)
- 配列(リスト)
- 数値
- 文字列
- 真理値
- null
https://www.json.org/json-en.html
datetimeやsetをJSON化する方法
理屈は後回しにし、まず先にdatetimeやsetを含んだ辞書をJSON化する方法から解説します。以下のコードのようにjson.dump関数の引数のdefaultに、サポート外の型が指定された場合の挙動を関数として指定するとJSON文字列として変換することが可能となります。
import json from datetime import datetime, date # サポート外の型が指定されたときの挙動を定義 def custom_default(o): if hasattr(o, '__iter__'): # イテラブルなものはリストに return list(o) elif isinstance(o, (datetime, date)): # 日時の場合はisoformatに return o.isoformat() else: # それ以外は文字列に return str(o) # 通常JSONにできないオブジェクトを含む辞書 mydict = {"int": 100, "str": "My String", "set": {1, 3, 5}, "list": [1, 2, 3, 4, 5], "dict": {"key1": 100, "key2": 200}, "range": range(10), "datetime": datetime.now(), "date": date.today(), "function": lambda x: x} json.dumps(mydict, default=custom_default) # {"int": 100, "str": "My String", "set": [1, 3, 5], "list": [1, 2, 3, 4, 5], "dict": {"key1": 100, "key2": 200}, "range": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "datetime": "2022-02-27T21:11:36.683126", "date": "2022-02-27", "function": "<function <lambda> at 0x000001253A540820>"}
上のコードではイテラブルなものはリストに、日付の場合はiso形式に、それ以外は文字列に変換するように指定しています。
JSONDecoder
ここから内部的な仕組みについて解説します。json.dump()は内部でJSONDecoderというクラスを使用しますが、その処理で以下のdefaultメソッドを呼び出します。デフォルトでは以下の実装の通りサポート外の変数が来た際、問答無用でTypeErrorが発生するように実装されています。ここをカスタマイズすることで特定の型の場合の挙動を指定することが可能となります。
# Python\Python3.8\Lib\json\encoder.pyより抜粋 def default(self, o): """Implement this method in a subclass such that it returns a serializable object for ``o``, or calls the base implementation (to raise a ``TypeError``). For example, to support arbitrary iterators, you could implement default like this:: def default(self, o): try: iterable = iter(o) except TypeError: pass else: return list(iterable) # Let the base class default method raise the TypeError return JSONEncoder.default(self, o) """ raise TypeError(f'Object of type {o.__class__.__name__} ' f'is not JSON serializable')
また、先程のdatetime、setをJSON化するコードですが以下のようにJSONEncoderを継承してdefaultメソッドをオーバーライドしても構いません。この場合は以下のコードのようにjson.dumpsの引数のclsにカスタマイズしたクラスを指定します。
import json from datetime import datetime, date class MyEncoder(json.JSONEncoder): def default(self, o): if hasattr(o, '__iter__'): # イテレータを持つ場合 return list(o) elif isinstance(o, (datetime, date)): # 日時の場合 return o.isoformat() else: return str(o) # 引数clsを指定する json.dumps(mydict, cls=MyEncoder)