Python: JSON でバイナリを扱う
バイナリデータをネットワーク送受信したい
バイナリデータをネットワーク越しに送受信したいことがあります。
普通にバイナリだけ送れば良いのですが、そのバイナリの属性値などを一緒に送りたいという要望がありました。
いろんな属性値を一度に送るのは JSON が便利なので、バイナリも JSON 形式に載せて送りたいと思ったのですが、ちょっと手こずったので記録しておきます。
テキストを JSON で送るには...
テキスト形式の dict を JSON として送信するのは、json モジュールを使って簡単に実現できます。
import json # 送りたい dict データ dictdata = { "str": "hoge", "num": 2, } # json.dumps を使って文字列に変換 strdata = json.dumps(dictdata) # encode を使ってネットワークで送れるバイナリ形式に変換 bindata = strdata.encode() # ネットワークに送る
しかし、このやり方でバイナリを送ろうとすると、json.dumps でエラーになります。
import json # 送りたい dict データ dictdata = { "str": "hoge", "bin": b"aa", } # json.dumps を使って文字列に変換 strdata = json.dumps(dictdata) # ここで下記のエラーになる # TypeError: b'aa' is not JSON serializable
こういうときは、バイナリを文字列に変換してから送ると良いようです。
バイナリを base64 エンコードする
Python ではないですが、以下を参考にしました。
これを Python で実現するには、そのままずばりの base64 モジュールを使います。
import json import base64 # 送りたい dict データ dictdata = { "str": "hoge", "bin": base64.b64encode(b"aa").decode('utf-8'), # base64 エンコード } # json.dumps を使って文字列に変換 (エラーにはならない) strdata = json.dumps(dictdata) # encode を使ってネットワークで送れるバイナリ形式に変換 bindata = strdata.encode() # ネットワークに送る
注意しなければならないのは、base64.b64encode の出力はバイト列になるということです。
つまり b64encode の出力は String ではないので、json.dumps で変換できません。
これを解消するために、b64encode したものをさらに .decode('utf-8') することで文字列に変換し、json.dumps で変換できるようにしています。
バイナリと文字列を何度行き来しているんだという感じですが...
受信するときは
上記のようにエンコードされたデータを受信するときは以下のような感じになります。
import json import base64 # ネットワークから受信したデータ: binrx # decode を使って、バイナリから文字列に変換 strrx = binrx.decode() # json.loads を使って dict に変換 dictrx = json.loads(strrx) # base64 を使って元のバイナリに変換 dictrx['bin'] = base64.b64decode(dictrx['bin'].encode())
ちょっと複雑ですが、これでバイナリを JSON に載せられるようになりました。