- 1. 概要
- 2. datetime
- 3. いわゆるオブジェクト
1. 概要
表題に書いてある、エラーには、「python」の「json」モジュールを使うと、必ずぶつかる問題と言ってもいいかと思います。
一度は、ぶつかって、わたしの場合、その後も何度も悩まされている問題です。
本ページは、下記のサイトを参考にさせていただきました。
「【Python】 json.dumpsのエラー対処: object of type is not json serializable」
「Pythonで型を取得・判定するtype関数, isinstance関数」
2. datetime
import datetime
import json
data = dict()
data['members'] = [
{ 'name': 'ほげほげ', 'number': 1, 'datetime': datetime.datetime.now()},
{ 'name': 'ふがふが', 'number': 2, 'datetime': datetime.datetime.now() },
]
with open('json02.json', mode='wt', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=2)
てなソースを書いて、実行するとこんなことになっちゃいます。
Traceback (most recent call last):
File "ソースファイル名.py", line 13, in <module>
json.dump(data, file, ensure_ascii=False, indent=2)
File "/パス/json/__init__.py", line 179, in dump
for chunk in iterable:
File "/パス/json/encoder.py", line 431, in _iterencode
yield from _iterencode_dict(o, _current_indent_level)
File "/パス/json/encoder.py", line 405, in _iterencode_dict
yield from chunks
File "/パス/json/encoder.py", line 325, in _iterencode_list
yield from chunks
File "/パス/json/encoder.py", line 405, in _iterencode_dict
yield from chunks
File "/パス/json/encoder.py", line 438, in _iterencode
o = _default(o)
File "/パス/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type datetime is not JSON serializable
要は、「『datetime』型は、シリアライズできまへんがな」ちゅうことですな。
これも参考サイトを元に、シリアライズできない型は、シリアライズ可能な方へ変換。
from datetime import datetime
import json
def support_datetime_default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError(repr(o) + " is not JSON serializable")
data = dict()
data['members'] = [
{ 'name': 'ほげほげ', 'number': 1, 'datetime': datetime.now() },
{ 'name': 'ふがふが', 'number': 2, 'datetime': datetime.now() },
]
with open('json03.json', mode='wt', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=2, default=support_datetime_default)
てなソースを書きます。
4~7行が、コールバック関数で、「json.dump」の際に、それを引数として与えています。
実行すると下記の内容のファイルが出力されます。
{
"members": [
{
"name": "ほげほげ",
"number": 1,
"datetime": "2021-06-25T13:55:38.313119"
},
{
"name": "ふがふが",
"number": 2,
"datetime": "2021-06-25T13:55:38.313124"
}
]
}
あるいは、
from datetime import datetime
import json
class DateTimeSupportJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
return super(DateTimeSupportJSONEncoder, self).default(o)
data = dict()
data['members'] = [
{ 'name': 'ほげほげ', 'number': 1, 'update': datetime.now()},
{ 'name': 'ふがふが', 'number': 2, 'update': datetime.now() },
]
with open('json04.json', mode='wt', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=2, cls=DateTimeSupportJSONEncoder)
てなソースを書きます。
これは、4~8行が、「JSONEncoder を継承したクラス」だそうで、そのクラスを使うように引数で指定しているのですな。
実行するとさっきと同様のファイルが出力されます。
3. いわゆるオブジェクト
前項に限らず、いわゆるスカラ変数以外のもの。
オブジェクトというのかしら。
てなものを出力しようとすると、基本、シリアライズできまへんがな・・・。
と言われます。
class Member:
## Member::__init__
# @brief 初期化関数
# @return 戻り値なし
def __init__(self, uri):
self.uri = uri # URI
self.status = False # 取得済
self.error = False # エラー
self.title = None # タイトル
self.document = None # ドキュメント
self.modified = None # 最終懇親日時
self.description = None # description 当面はタイトルを適用
self.body = None # body
## リンク 先の配列
self.links = []
上記は、わたしが実際に使っている、オブジェクトなのですが。
こいつがはいっている、配列をダンプしようとすると(「tree」という変数が、対象の配列)。
with open('../contents.json', mode='wt', encoding='utf-8') as file:
json.dump(tree, file, ensure_ascii=False, indent=2)
下記のようなことになる(一部略)。
File "/usr/local/lib/python3.9/json/__init__.py", line 179, in dump
for chunk in iterable:
File "/usr/local/lib/python3.9/json/encoder.py", line 429, in _iterencode
yield from _iterencode_list(o, _current_indent_level)
File "/usr/local/lib/python3.9/json/encoder.py", line 325, in _iterencode_list
yield from chunks
File "/usr/local/lib/python3.9/json/encoder.py", line 438, in _iterencode
o = _default(o)
File "/usr/local/lib/python3.9/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Member is not JSON serializable
こういう場合は、オブジェクトに「__dict__」ていう特殊属性なるものをのをつけてやれば。
辞書型のように解釈してダンプしてくれるようです。
すなわち。
class MemberSupportJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Member.Member):
return o.__dict__
return super(MemberSupportJSONEncoder, self).default(o)
with open('../contents.json', mode='wt', encoding='utf-8') as file:
json.dump(tree, file, ensure_ascii=False, indent=2, cls=MemberSupportJSONEncoder)
と書いてやれば、うまくダンプできるのでした。
「class MemberSupportJSONEncode」の記述で、インスタンスが「Member.Member」でないときの処理を書いていないのが不十分なのですが。
今んとこ(2023年10月18日)、これで動いているので、これでいいのだ。
これ、本当は、「json5」で書きたいのですが・・・。
「json5」のモジュールには、「JSONEncoder」もしくは「JSON5Encoder」というものがないと言われるのだ。
逃げ道で、上記に続けて。
with open('../contents.json', mode='r', encoding='utf-8') as file:
load = json5.load(file)
with open('../contents.json', mode='wt', encoding='utf-8') as file:
json5.dump(load, file, ensure_ascii=False, indent=2)
と書くことで、最終的なファイルを「json5」形式にしました。
|