環境
- Python 3.11.2
- pandas 2.0.1
- numpy 1.24.2
はじめに
pandas.DataFrame
から取得した情報を加工して、JSON出力したいです。
以下のPythonスクリプトを実行すると、json.dumps
で"TypeError: Object of type int64 is not JSON serializable"というエラーが発生しました。
import pandas
from dataclasses import dataclass, asdict
import json
@dataclass
class Info:
year: int
score: int
df = pandas.DataFrame({"year": [2020, 2021, 2022], "score": [2, 4, 6]})
print(df.dtypes)
last_row = df.iloc[-1]
last_info = Info(last_row["year"], last_row["score"])
# last_infoを使って何かする
...
result = last_info
print(f"{type(result.year)=}, {type(result.score)=}")
print(json.dumps(asdict(result)))
$ python sample.py
year int64
score int64
dtype: object
type(result.year)=<class 'numpy.int64'>, type(result.score)=<class 'numpy.int64'>
Traceback (most recent call last):
File "/home/vagrant/Documents/study/20230511/sample.py", line 25, in <module>
print(json.dumps(asdict(result)))
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 200, in encode
chunks = self.iterencode(o, _one_shot=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 258, in iterencode
return _iterencode(o, 0)
^^^^^^^^^^^^^^^^^
File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 180, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type int64 is not JSON serializable
エラーの原因
numpy.int64
はint
のサブクラスでないから、json.dumps
でTypeError
が発生しました。
もっとシンプルなコードで確認してみます。
In [27]: json.dumps(numpy.int64(1))
---------------------------------------------------------------------------
...
TypeError: Object of type int64 is not JSON serializable
In [28]: issubclass(numpy.int64, int)
Out[28]: False
numpy.int
がint
のサブクラスでない理由
Pythonの整数は上限がなく65bit以上の整数を表現できます。一方numpy.int64
は64bitまでしか整数を表現できません。したがって、numpy.int64
はint
のサブクラスではありません。
numpy.int64
以外はJSON出力できるのか?
json.dumps
/ json.dump
は以下の変換表に従って処理します。
numpy.int64
以外のnumpyの型が、JSON出力できるかを確認しました。
numpy.float64
In [84]: issubclass(numpy.float64, float)
Out[84]: True
In [85]: json.dumps(numpy.float64(1.2))
Out[85]: '1.2'
Pythonのfloatは64bitで、numpy.float64
と表現できるbit数が同じです。だからnumpy.float64
はfloat
のサブクラスになれているのかもしれません。
※numpy.float64
がfloat
のサブクラスになっている明確な理由は分かりませんが、きっとサブクラスにした方がが便利だからでしょう。
numpy.bool_
In [86]: issubclass(numpy.bool_, bool)
Out[86]: False
In [87]: issubclass(bool, int)
Out[87]: True
bool
はint
クラスのサブクラスです。したがって、numpy.bool_
はbool
のサブクラスではありませんでした。
bool クラスは int クラスの派生クラスです (数値型 int, float, complex を参照してください)。このクラスからさらに派生することはできません。1
numpy.str_
In [88]: issubclass(numpy.str_, str)
Out[88]: True
In [89]: json.dumps(numpy.str_("a"))
Out[89]: '"a"'
numpy.ndarray
In [100]: issubclass(numpy.ndarray, list)
Out[100]: False
上述のエラーを解決する方法
(a) int()
関数でint
に変換する
last_info = Info(int(last_row["year"]), int(last_row["score"]))
(b) pandasのto_dict
やto_json
を利用する
In [160]: last_row.to_json()
Out[160]: '{"year":2022,"score":6}'
In [161]: json.dumps(last_row.to_dict())
Out[161]: '{"year": 2022, "score": 6}'
(c) json.dumps
関数のdefault
引数に、変換する処理が記載された関数を指定する
default
引数に指定した関数は、Serializeできないオブジェクトに対して呼ばれます。
import numpy
import json
def default(o):
print(f"{type(o)=}")
if isinstance(o, numpy.int64):
return int(o)
elif isinstance(o, numpy.bool_):
return bool(o)
elif isinstance(o, numpy.ndarray):
return list(o)
raise TypeError(repr(o) + " is not JSON serializable")
print(json.dumps(numpy.int64(1), default=default))
print(json.dumps(numpy.bool_(True), default=default))
print(json.dumps(numpy.array([1.2, 2.3]), default=default))
$ python sample1.py
type(o)=<class 'numpy.int64'>
1
type(o)=<class 'numpy.bool_'>
true
type(o)=<class 'numpy.ndarray'>
[1.2, 2.3]
(d) json.dumps
関数のcls
引数に、JSONEncoder
のサブクラスを指定する
import json
from json import JSONEncoder
class NumpyJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, numpy.int64):
return int(o)
elif isinstance(o, numpy.bool_):
return bool(o)
elif isinstance(o, numpy.ndarray):
return list(o)
return JSONEncoder.default(self, o)
print(json.dumps(numpy.int64(1), cls=NumpyJSONEncoder))
print(json.dumps(numpy.bool_(True), cls=NumpyJSONEncoder))
print(json.dumps(numpy.array([1.2, 2.3]), cls=NumpyJSONEncoder))
$ python sample2.py
1
true
[1.2, 2.3]
参考にしたサイト