子育てしながらエンジニアしたい

現在 2 歳女の子の子育て中エンジニアによる、技術系 + 日常系ブログ。

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 ではないですが、以下を参考にしました。

qiita.com

これを 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 に載せられるようになりました。