emitjson
emitjson
というPythonモジュールを公開した。
emitjson
という名前にしたが、じつはJSONは出力しない。 任意のオブジェクトを、json モジュールがサポートしているオブジェクトに変換するときに使うユーティリティだ。
例えば、日付とバイナリデータが入った辞書のリストからJSONを作成しようと思うと、だいたいこんな感じになる。
values = [] for d in src_vlaues: date = d['date'].isoformat() bin = base64.b64encode(d['bin']) values.append({'date':date, 'bin':bin}) json.dumps(values)
意外とめんどくさい。このぐらいなら「めんどくさい」で済むが、要素の数が多いとめんどくささが半端なくなってくる。 json.JSONEncoder をカスタマイズする手はあるが、それもなかなか面倒だ。
そこで、自前でデータを変換する仕組みを考えてみた。collections.abc と、Python3.4で導入された、functools.singledispatch を使えば、簡単に拡張性の高い変換ユーティリティを作れそうだ。
作ってみると、あっさりといい感じに仕上がった。上の処理は次のように書ける。
import emitjson myrepo = emitjson.repository() # emitjson リポジトリを作成 # リポジトリに bytes型の変換関数を登録 @myrepo.register(bytes) def conv_bytes(obj): # bytes型をBase64文字列に変換 return base64.b64encode(obj).decode('ascii') json.dumps(myrepo(src_vlaues))
emitjson
では、リストの要素や辞書のキー・値を走査し、登録した種類のオブジェクトがあれば、自動的に変換を行う。デフォルトでは シーケンス型・マッピング型の操作をサポートしており、datetime.date
, datetime.datetime
の要素があれば ISO 8601形式の文字列に変換する。
emitjson.repository()
をデコレータとして、独自の変換関数を登録できる。上の例では、conv_bytes()
関数は、bytes
型のオブジェクトを文字列に変換する。
また、DjangoやSQLAlchemyのモデルなどを辞書オブジェクトに変換する処理は、次のように定義できる。
class Test: def __init__(self): self.prop1 = 'spam' self.prop2 = 'ham' # Test1クラスを変換する処理の定義 @myrepo.register(Test1) class Test1Converter(emitjson.ObjConverter): prop1 = attr # obj.prop1 を取得 prop_a = attr('prop2') # obj.prop2 を取得 json.dumps(myrepo([Test()]))
同じように、オブジェクトをJSON化可能な形式に変換するモジュールに、bpmappers がある。
bpmappers
では主にクラスインスタンスを辞書に変換する機能が主となっているが、emitjson
では簡単に任意のオブジェクトを変換できるようになっている。また、コンテナオブジェクトを変換するとその内容を自動的にスキャンし、再帰的に変換を行う点も大きな違いだ。ただし、 emitjson
は Python3.4 以降でなければ動かない。Python3.4より前、とくにPython2.xで利用する場合は bpmappers
が必須となる。測定はしていないが、おそらくパフォーマンスもbpmappers
の方が優れていると思う。