JSONデータで情報を受信していると、受信したJSONデータそのものをリアルタイムでそのまま表示させたいことがままあります。
json.dumpsメソッドにindent指定して、テキスト表示すれば、きれいに改行して表示すことが簡単にできます。
しかし、大量のJSONデータが全て展開されていると、所望のデータにたどり着くまでスクロールすることになります。あるいはエディタに展開して検索ですね。
TreeViewで閉じられた状態のほうが便利かなと思い、今回のコードを作りました。
データ準備
記事のために利用させていただいたJSONデータは、以下のサイトです。
京都市オープンデータポータルサイト
WEBブラウザのURLにリクエスト例を入力すると、JSONデータが表示されますので、これをテキストエディタに全選択コピーをして、sample.jsonと名付けて保存してください。
ソースコード
ソースコードファイルと同じフォルダに準備したsample.jsonを保存してください。
サンプルコード
import json
import pathlib
import tkinter as tk
import tkinter.ttk as ttk
class TkJSON_TreeView():
def __init__(self, root, max_item_length: int = 300, max_value_length: int = 500):
inFrame = tk.Frame(root)
tree = ttk.Treeview(
inFrame,
height=30,
columns=["VALUE", ],
)
tree_h_scroll = ttk.Scrollbar(
inFrame,
orient=tk.HORIZONTAL,
command=tree.xview
)
tree_v_scroll = ttk.Scrollbar(
inFrame,
orient=tk.VERTICAL,
command=tree.xview
)
tree['xscrollcommand'] = tree_h_scroll.set
tree['yscrollcommand'] = tree_v_scroll.set
tree.column("#0", width=200)
tree.column("VALUE", width=100)
tree.heading("#0", text="構造")
tree.heading("VALUE", text="値")
tree.bind('<<TreeviewSelect>>', self.select_event)
self.inFrame = inFrame
self.tree = tree
self.tree_h_scroll = tree_h_scroll
self.tree_v_scroll = tree_v_scroll
self.max_item_length = max_item_length
self.max_value_length = max_value_length
def select_event(self, envet):
print(self.tree.selection())
def run(self, json_dict: dict):
tree = self.tree
def _next_tree_(tree, head, text, value, deep=0, text_max=0, value_max=0):
deep += 1
_deep = None
if isinstance(value, dict):
for k, v in value.items():
tid = tree.insert(head, tk.END, text=k)
text_max = max(text_max, len(str(k)))
_deep_, text_max, value_max = _next_tree_(
tree,
tid,
text=k,
value=v,
deep=deep,
text_max=text_max,
value_max=value_max
)
if _deep:
_deep = max(_deep, _deep_)
else:
_deep = _deep_
elif isinstance(value, list):
for i, c_val in enumerate(value):
if isinstance(c_val, dict):
tid = tree.insert(head, tk.END, text=str(i))
text_max = max(text_max, len(str(i)))
_deep_, text_max, value_max = _next_tree_(
tree, tid,
text=str(i),
value=c_val,
deep=deep,
text_max=text_max,
value_max=value_max
)
if _deep:
_deep = max(_deep, _deep_)
else:
_deep = _deep_
elif isinstance(c_val, list):
# List内のListについては未対応
pass
else:
if c_val is None:
c_val = "None"
c_val = str(c_val)
tree.insert(head, tk.END, text=str(i), value=c_val)
text_max = max(text_max, len(str(i)))
value_max = max(value_max, len(c_val))
else:
if value is None:
value = "None"
value = str(value)
text = str(text)
tree.insert(head, tk.END, text=text, value=value)
text_max = max(text_max, len(text))
value_max = max(value_max, len(value))
if _deep:
deep = _deep
return deep, text_max, value_max
deep, text_max, value_max = _next_tree_(tree, '', '', json_dict)
item_length = deep*4*8
value_length = value_max*8
if self.max_item_length:
if isinstance(self.max_item_length, int):
if item_length > self.max_item_length:
item_length = self.max_item_length
tree.column("#0", width=item_length)
if self.max_value_length:
if isinstance(self.max_value_length, int):
if value_length > self.max_value_length:
value_length = self.max_value_length
tree.column("VALUE", width=value_length)
tree.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.tree_h_scroll.grid(row=1, column=0, sticky=tk.EW)
self.tree_v_scroll.grid(row=0, column=1, sticky=tk.NS)
self.inFrame.columnconfigure(0, weight=1)
self.inFrame.rowconfigure(0, weight=1)
self.inFrame.pack()
file_path = pathlib.Path(__file__).parent / 'sample.json'
print(file_path)
with open(file_path, 'r') as f:
data = f.read()
data = json.loads(data)
# トップレベルウィンドウ作成
root = tk.Tk()
TkJSON_TreeView(root).run(data)
# メインループ
root.mainloop()
実行結果
実行結果は以下のように、データ構造の深さに合わせて、ツリーは深くなります。
listは、index順に数字で並べられます。数値がキーとなっている辞書型と区別できません。
listの中に直接listという構造なっている場合、対応していません。
定期的に更新したい場合には、TreeViewを一旦クリアして再度作り直す必要があり、今まで展開して表示していた部分は閉じてしまいます。工夫が必要になります。
select_event関数で、現在の選択アイテムを取得しているので、これを利用して、再展開させるなど工夫が必要です。