微忘録

好奇心に記憶力がついていかない人のブログ

numpyオブジェクトをjson.dumpできるようにエンコーダーを拡張する方法

最近、友人の運営する大学講義の検索サイトを、機械学習で良い感じにしてます。

地味に悩んだのが「JSON形式でのデータ入出力」の部分。具体的には、「JSON形式で渡されたデータに、推定結果の一部numpyオブジェクトを加えたJSONデータを返す」作業。

Pythonなど動的言語はデータ型をよしなに判断してくれますが、少し例外に触れた時には指定してあげる必要があります。
今回はその例外のためにクラスを自作して指定する方法を備忘録。

問題

何も引数に指定せず、ただ以下の様にjson.dumps()メソッドにnumpy.int64などを渡すと、以下の様なエラーに遭遇する。

json.dumps(numpy.int64(111))
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-12-c21d581d4bed> in <module>()
----> 1 json.dumps(np.int64(111))


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    229         cls is None and indent is None and separators is None and
    230         default is None and not sort_keys and not kw):
--> 231         return _default_encoder.encode(obj)
    232     if cls is None:
    233         cls = JSONEncoder


/usr/local/var/pyenv/versions/anaconda3-5.0.0/lib/python3.6/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)


~~~

TypeError: Object of type 'int64' is not JSON serializable

この一文は「JSONモジュールの既存エンコーダーに対応している型じゃないから変換無理です」という話

TypeError: Object of type 'int64' is not JSON serializable

なので既存エンコーダーに、自前のエンコード用クラスを与えて拡張する必要があります。

解決策

以下のような、型の種類ごとに変換を行うクラスを作成して、JSONモジュールに対応する型にします。

  • isinstance(object, class)で、一致する場合はreturn以下で変換して返す。
  • numpy.integerint型へ
  • numpy.floatingfloat型へ
  • numpy.ndarraylist型へ

(今回はint型の変換だけで良いのですが、オマケでその他も加えておきます。)

import numpy
import json

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, numpy.integer):
            return int(obj)
        elif isinstance(obj, numpy.floating):
            return float(obj)
        elif isinstance(obj, numpy.ndarray):
            return obj.tolist()
        else:
            return super(MyEncoder, self).default(obj)

そしてjson.dump()cls引数にクラスを渡してあげます。

json.dumps(numpy.int64(111),cls = MyEncoder)
'111'

解決!

最後に

JSON形式で数値を扱うこと自体ナンセンスかもしれませんが、遭遇しやすい問題かなと思うので載せておきました。 良き分析ライフを御過ごしください。

参考記事

以下記事を参考にしております。先人の知恵に感謝です。