読者です 読者をやめる 読者になる 読者になる

特定の要素だけインデントしないJSONエンコーダー (Python3)

自分だけかもしれないが、ちょっとした自作プログラムでデータ編集用にリストビューやツリービュー的な GUI が必要だが面倒臭いので作りたくない場合、

  1. プログラムを実行
  2. JSON 形式でデータを出力
  3. テキストエディタJSONファイルを編集
  4. プログラムで 3 のデータを読み込んで何らかの処理を行う

というのをやる。

自分は Python でプログラムを書くので Python オブジェクトを json.dump() でJSON 形式にエンコードして保存する。しかしこのメソッドは「全ての要素をインデントする」か「全ての要素をインデントしない」かのどちらかしか選べない。インデント無しだと当然見辛いが、全てインデントされているのもちょっと見辛いし編集しにくかったりする。

個人的には

  • 特定の要素だけインデントしない
  • ネストが深さn以上ならインデントしない
  • 子要素がn個以下の要素はインデントしない

などの設定が出来ると嬉しい。

ちょっと調べてみたけれど、このような設定の出来る prettyprinter は見つからなかった。ただ、 PythonJSON エンコーダーを拡張することで、特定のオブジェクトだけインデント無しに出来る方法はあるようだ。

Can I implement custom indentation for pretty-printing in Python’s JSON module? - Stack Overflow

次のPythonスクリプトは上記URLの一番下の回答コードをPython3で動くように微修正したもの。

# from http://stackoverflow.com/questions/13249415/can-i-implement-custom-indentation-for-pretty-printing-in-python-s-json-module

import json
import uuid

class NoIndent(object):
	def __init__(self, value):
		self.value = value

class NoIndentEncoder(json.JSONEncoder):
	def __init__(self, *args, **kwargs):
		super(NoIndentEncoder, self).__init__(*args, **kwargs)
		self.kwargs = dict(kwargs)
		del self.kwargs['indent']
		self._replacement_map = {}

	def default(self, o):
		if isinstance(o, NoIndent):
			key = uuid.uuid4().hex
			self._replacement_map[key] = json.dumps(o.value, **self.kwargs)
			return "@@%s@@" % (key,)
		else:
			return super(NoIndentEncoder, self).default(o)

	def encode(self, o):
		result = super(NoIndentEncoder, self).encode(o)
		for k, v in self._replacement_map.items():
			result = result.replace('"@@%s@@"' % (k,), v)
		return result

Python デフォルトの JSON エンコーダーを使ってインデント 4 でエンコードすると全ての要素がインデントされる

obj = {"a": 1, "b": [1, 2, 3], "c":{"A": [1, 2, [3, 4, 5]], "B": "XYZ", "C": 3}}
print(json.dumps(obj, sort_keys=True, indent=4))

---------- 結果 ----------
{
    "a": 1,
    "b": [
        1,
        2,
        3
    ],
    "c": {
        "A": [
            1,
            2,
            [
                3,
                4,
                5
            ]
        ],
        "B": "XYZ",
        "C": 3
    }
}

インデントしたくない要素を先ほど定義した NoIndent クラスのオブジェクトにし、 JSON エンコーダーに NoIndentEncoder クラスを指定すると次のようになる。

obj = {"a": 1, "b": [1, 2, 3], "c": NoIndent({"A": [1, 2, [3, 4, 5]], "B": "XYZ", "C": 3})}
print(json.dumps(obj, sort_keys=True, indent=4, cls=NoIndentEncoder))

---------- 結果 ----------
{
    "a": 1,
    "b": [
        1,
        2,
        3
    ],
    "c": {"A": [1, 2, [3, 4, 5]], "B": "XYZ", "C": 3}
}