singledispatchとjson.dumpが相性良いかも?

jsonのencodeエラーについては昔に書いたこの辺を見てもらうとして。

今回の主題は、json.dumpsに渡すdefaultの関数としてfunctools.singledispatchが有用かもという話。

使いかた

singledispatchを利用したモジュールを定義

例えば以下のようなextjsonモジュールを定義してあげる。

extjson.py

import json
from functools import (
    singledispatch,
    partial,
)


@singledispatch
def encode(o):
    raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)


register = encode.register

dump = partial(json.dump, indent=2, ensure_ascii=False, sort_keys=True, default=encode)
dumps = partial(json.dumps, indent=2, ensure_ascii=False, sort_keys=True, default=encode)
load = json.load
loads = json.loads

extjsonのdump,dumpsには、defaultに定義したencode()を渡してあげる。このencode()はsingledispatchを利用したもの。

普通に使う

特に何も設定していなくても普通のjsonモジュールと同じように使える。

import extjson

d = {
    "name": "foo",
    "age": 20
}

print(extjson.dumps(d))

# {
#   "age": 20,
#   "name": "foo"
# }

通常ならTypeErrorが出るものに対して使う

通常なら意図しなかった型の値が渡された場合にはTypeErrorになる。このような型に対する対応をsingledispatch.registerで良い感じに使う側でできる。 たとえば、datetimeオブジェクトなどは対応していない型。

import datetime as dt
import extjson


@extjson.register(dt.datetime)
def encode_datetime(o):
    return o.isoformat()


d = {
    "name": "foo",
    "birth": dt.datetime.now(),
}
print(extjson.dumps(d))

# {
#   "birth": "2017-09-24T05:28:16.413651",
#   "name": "foo"
# }

悪くないような気がする。

追記:singledispatchはabcにも対応している

singledispatchはabcにも対応しているので以下の様な形で仮想継承を経由しても使える。

date,datetimeを自分で定義したStringerというクラスに所属させてみた。

import datetime as dt
import extjson
import abc


class Stringer(abc.ABC):
    pass


Stringer.register(dt.datetime)
Stringer.register(dt.date)


@extjson.register(Stringer)
def encode_stringer(o):
    return str(o)


d = {
    "name": "foo",
    "birth": dt.datetime.now(),
    "birthday": dt.date.today(),
}
print(extjson.dumps(d))

# {
#   "birth": "2017-09-24 12:20:27.933120",
#   "birthday": "2017-09-24",
#   "name": "foo"
# }